<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>JVM虚拟机基础篇 | Jixer的小屋</title><meta name="author" content="Jixer"><meta name="copyright" content="Jixer"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="初始JVM什么是JVMJVM 全称是 Java Virtual Machine，中文译名 Java虚拟机。JVM 本质上是一个运行在计算机上的程序，他的职责是运行Java字节码文件 Java源代码执行流程如下：  分为三个步骤： 1、编写Java源代码文件 2、使用Java编译器（javac命令）将源代码编译成Java字节码文件 3、使用Java虚拟机加载并运行Java字节码文件，此时会启动一个新">
<meta property="og:type" content="article">
<meta property="og:title" content="JVM虚拟机基础篇">
<meta property="og:url" content="http://www.lijunxi.site/posts/3771679189/index.html">
<meta property="og:site_name" content="Jixer的小屋">
<meta property="og:description" content="初始JVM什么是JVMJVM 全称是 Java Virtual Machine，中文译名 Java虚拟机。JVM 本质上是一个运行在计算机上的程序，他的职责是运行Java字节码文件 Java源代码执行流程如下：  分为三个步骤： 1、编写Java源代码文件 2、使用Java编译器（javac命令）将源代码编译成Java字节码文件 3、使用Java虚拟机加载并运行Java字节码文件，此时会启动一个新">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://q1.qlogo.cn/g?b=qq&nk=2770063826&s=640">
<meta property="article:published_time" content="2024-04-07T06:15:19.000Z">
<meta property="article:modified_time" content="2024-05-07T08:06:29.291Z">
<meta property="article:author" content="Jixer">
<meta property="article:tag" content="Java">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://q1.qlogo.cn/g?b=qq&nk=2770063826&s=640"><link rel="shortcut icon" href="/img/logo/favicon.ico"><link rel="canonical" href="http://www.lijunxi.site/posts/3771679189/index.html"><link rel="preconnect"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css?v=4.13.0"><link rel="stylesheet" href="/pluginsSrc/@fortawesome/fontawesome-free/css/all.min.css?v=6.5.1"><link rel="stylesheet" href="/pluginsSrc/@fancyapps/ui/dist/fancybox/fancybox.css?v=5.0.33" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = {
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":true,"top_n_per_article":1,"unescape":false,"languages":{"hits_empty":"找不到您查询的内容：${query}","hits_stats":"共找到 ${hits} 篇文章"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  dateSuffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  infinitegrid: {
    js: '/pluginsSrc/@egjs/infinitegrid/dist/infinitegrid.min.js?v=4.11.1',
    buttonText: '加载更多'
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false,
  percent: {
    toc: true,
    rightside: false,
  },
  autoDarkmode: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'JVM虚拟机基础篇',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2024-05-07 16:06:29'
}</script><script>(win=>{
      win.saveToLocal = {
        set: (key, value, ttl) => {
          if (ttl === 0) return
          const now = Date.now()
          const expiry = now + ttl * 86400000
          const item = {
            value,
            expiry
          }
          localStorage.setItem(key, JSON.stringify(item))
        },
      
        get: key => {
          const itemStr = localStorage.getItem(key)
      
          if (!itemStr) {
            return undefined
          }
          const item = JSON.parse(itemStr)
          const now = Date.now()
      
          if (now > item.expiry) {
            localStorage.removeItem(key)
            return undefined
          }
          return item.value
        }
      }
    
      win.getScript = (url, attr = {}) => new Promise((resolve, reject) => {
        const script = document.createElement('script')
        script.src = url
        script.async = true
        script.onerror = reject
        script.onload = script.onreadystatechange = function() {
          const loadState = this.readyState
          if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
          script.onload = script.onreadystatechange = null
          resolve()
        }

        Object.keys(attr).forEach(key => {
          script.setAttribute(key, attr[key])
        })

        document.head.appendChild(script)
      })
    
      win.getCSS = (url, id = false) => new Promise((resolve, reject) => {
        const link = document.createElement('link')
        link.rel = 'stylesheet'
        link.href = url
        if (id) link.id = id
        link.onerror = reject
        link.onload = link.onreadystatechange = function() {
          const loadState = this.readyState
          if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
          link.onload = link.onreadystatechange = null
          resolve()
        }
        document.head.appendChild(link)
      })
    
      win.activateDarkMode = () => {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = () => {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
        if (t === 'dark') activateDarkMode()
        else if (t === 'light') activateLightMode()
      
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
      const detectApple = () => {
        if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
          document.documentElement.classList.add('apple')
        }
      }
      detectApple()
    })(window)</script><link rel="stylesheet" href="/css/custom-all-min.css"><link rel="stylesheet" href="/css/custom-fancybox-min.css"><link rel="stylesheet" href="/css/custom-share-min.css"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="loading-box"><div class="loading-left-bg"></div><div class="loading-right-bg"></div><div class="spinner-box"><div class="configure-border-1"><div class="configure-core"></div></div><div class="configure-border-2"><div class="configure-core"></div></div><div class="loading-word">加载中...</div></div></div><script>(()=>{
  const $loadingBox = document.getElementById('loading-box')
  const $body = document.body
  const preloader = {
    endLoading: () => {
      $body.style.overflow = ''
      $loadingBox.classList.add('loaded')
    },
    initLoading: () => {
      $body.style.overflow = 'hidden'
      $loadingBox.classList.remove('loaded')
    }
  }

  preloader.initLoading()
  window.addEventListener('load',() => { preloader.endLoading() })

  if (false) {
    document.addEventListener('pjax:send', () => { preloader.initLoading() })
    document.addEventListener('pjax:complete', () => { preloader.endLoading() })
  }
})()</script><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="" data-original="https://q1.qlogo.cn/g?b=qq&amp;nk=2770063826&amp;s=640" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">52</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">19</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div><hr class="custom-hr"/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fa fa-graduation-cap"></i><span> 文章</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw fa fa-archive"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw fa fa-folder-open"></i><span> 归档</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/links/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><span id="blog-info"><a href="/" title="Jixer的小屋"><span class="site-name">Jixer的小屋</span></a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search" href="javascript:void(0);"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fa fa-graduation-cap"></i><span> 文章</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw fa fa-archive"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw fa fa-folder-open"></i><span> 归档</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/links/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">JVM虚拟机基础篇</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2024-04-07T06:15:19.000Z" title="发表于 2024-04-07 14:15:19">2024-04-07</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2024-05-07T08:06:29.291Z" title="更新于 2024-05-07 16:06:29">2024-05-07</time></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">21.4k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>72分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="JVM虚拟机基础篇"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h2 id="初始JVM"><a href="#初始JVM" class="headerlink" title="初始JVM"></a>初始JVM</h2><h3 id="什么是JVM"><a href="#什么是JVM" class="headerlink" title="什么是JVM"></a>什么是JVM</h3><p>JVM 全称是 Java Virtual Machine，中文译名 Java虚拟机。JVM 本质上是一个运行在计算机上的程序，他的职责是运行Java字节码文件</p>
<p>Java源代码执行流程如下：</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/07/pFqoAGd.png"></p>
<p>分为三个步骤：</p>
<p>1、编写Java源代码文件</p>
<p>2、使用Java编译器（javac命令）将源代码编译成Java字节码文件</p>
<p>3、使用Java虚拟机加载并运行Java字节码文件，此时会启动一个新的进程</p>
<h3 id="JVM功能"><a href="#JVM功能" class="headerlink" title="JVM功能"></a>JVM功能</h3><h4 id="解释运行"><a href="#解释运行" class="headerlink" title="解释运行"></a>解释运行</h4><p>字节码文件包含了字节码指令，计算器无法直接执行，虚拟机就将字节码文件中的<strong>字节码</strong>指令实时的解释成<strong>机器码</strong>，让计算机执行</p>
<h4 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h4><ul>
<li>自动为对象、方法等分配内存</li>
<li>自动的垃圾回收机制，回收不再使用的垃圾（相比于c&#x2F;c++的优势）</li>
</ul>
<h4 id="即时编译"><a href="#即时编译" class="headerlink" title="即时编译"></a>即时编译</h4><p>对热点代码进行优化，提升执行效率。<strong>即时编译</strong>可以说是提升Java程序性能最核心的手段</p>
<p>虚拟机在运行过程中，若发现某一个方法是热点代码（被非常高频率调用），即使编译器就会优化这段代码并将优化后的机器码保存在内存中；如果第二次再去执行这段代码，Java虚拟机就会从内存中取出直接调用。这样节省了一次解释的步骤，效率较高</p>
<p>Java通过即时编译器获得了接近C&#x2F;C++语言的性能，在某些特定的场景下甚至可以实现超越。</p>
<h4 id="Java性能和跨平台性"><a href="#Java性能和跨平台性" class="headerlink" title="Java性能和跨平台性"></a>Java性能和跨平台性</h4><p>Java性能低的主要原因：在程序运行的过程中，Java虚拟机会实时的将<strong>字节码</strong>指令解释成<strong>机器码</strong>，这个过程可能会反复执行，所以效率低</p>
<p>C和C++性能高的原因：在程序执行的过程中，只需要把<strong>源代码</strong>编译成<strong>可执行文件</strong>，可执行文件中就包括了<strong>机器码</strong>，程序在运行的时候就不用实时的解释，所以性能高</p>
<p>跨平台性：正是因为Java的选择这种效率低的方式，主要是为了<strong>实现跨平台性</strong>。同一份字节码文件，交给不同的Windows和Linux上的Java虚拟机上进行解释，这样就能得到不同平台上的<strong>机器码</strong>。实现了<strong>编写一次，多次运行</strong>的目标；但如果是C&#x2F;C++要想在不同平台上运行就需要将原代码先进行编译</p>
<h3 id="常见的JVM"><a href="#常见的JVM" class="headerlink" title="常见的JVM"></a>常见的JVM</h3><table>
<thead>
<tr>
<th>名称</th>
<th>作者</th>
<th>支持版本</th>
<th>社区活跃度（github star）</th>
<th>特性</th>
<th>适用场景</th>
</tr>
</thead>
<tbody><tr>
<td>HotSpot (Oracle JDK版)</td>
<td>Oracle</td>
<td>所有版本</td>
<td>高(闭源)</td>
<td>使用最广泛，稳定可靠，社区活跃JIT支持Oracle JDK默认虚拟机</td>
<td>默认</td>
</tr>
<tr>
<td>HotSpot (Open JDK版)</td>
<td>Oracle</td>
<td>所有版本</td>
<td>中(16.1k)</td>
<td>同上开源，Open JDK默认虚拟机</td>
<td>默认对JDK有二次开发需求</td>
</tr>
<tr>
<td>GraalVM</td>
<td>Oracle</td>
<td>11, 17,19企业版支持8</td>
<td>高（18.7k）</td>
<td>多语言支持高性能、JIT、AOT支持</td>
<td>微服务、云原生架构需要多语言混合编程</td>
</tr>
<tr>
<td>Dragonwell JDK龙井</td>
<td>Alibaba</td>
<td>标准版 8,11,17扩展版11,17</td>
<td>低(3.9k)</td>
<td>基于OpenJDK的增强高性能、bug修复、安全性提升JWarmup、ElasticHeap、Wisp特性支持</td>
<td>电商、物流、金融领域对性能要求比较高</td>
</tr>
<tr>
<td>Eclipse OpenJ9 (原 IBM J9)</td>
<td>IBM</td>
<td>8,11,17,19,20</td>
<td>低(3.1k)</td>
<td>高性能、可扩展JIT、AOT特性支持</td>
<td>微服务、云原生架构</td>
</tr>
</tbody></table>
<h3 id="Java虚拟机的组成"><a href="#Java虚拟机的组成" class="headerlink" title="Java虚拟机的组成"></a>Java虚拟机的组成</h3><p>Java虚拟机主要分为以下几个组成部分：</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/07/pFq7vxs.png"></p>
<h3 id="字节码文件的组成"><a href="#字节码文件的组成" class="headerlink" title="字节码文件的组成"></a>字节码文件的组成</h3><ul>
<li>基本信息：魔数、字节码文件对应的Java版本号、访问标识(public final等等)、父类和接口信息</li>
<li>常量池： 保存了字符串常量、类或接口名、字段名，主要在字节码指令中使用</li>
<li>字段： 当前类或接口声明的字段信息</li>
<li>方法： 当前类或接口声明的方法信息，核心内容为方法的字节码指令</li>
<li>属性： 类的属性，比如源码的文件名、内部类的列表等</li>
</ul>
<p>####基本信息</p>
<p><strong>魔数</strong></p>
<p>文件无法通过文件拓展名来确定文件类型，文件拓展名可以随意修改但却不影响文件内容。软件会用文件的头几个字节（文件头）去校验文件的类型，如果文件不支持该拓展就会报错</p>
<p>常见的文件格式检验方式如下：</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/07/pFqHVz9.png"></p>
<p><strong>主副版本号</strong></p>
<p>主副版本号指的是编译字节码文件时使用的JDK版本号</p>
<p>主版本号用来标识大版本号，副版本号是当主版本号相同时作为区分不同版本的标识，一般只需要关心主版本号</p>
<p>JDK1.0-1.1使用了45.0-45.3，1.2之后大版本号计算方法就是 : 主版本号 – 44，比如主版本号52就是JDK8</p>
<h4 id="常量池"><a href="#常量池" class="headerlink" title="常量池"></a>常量池</h4><p>字符串常量池的作用：避免相同的内容重复定义，节省空间。如下图，常量池中定义了一个字符串，字符串的字面量值为123。</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/07/pFqHfoT.png"></p>
<p>当多个定义相同的字符串的时候，在内存中使用时其实只需要保存一份</p>
<p>常量池中的数据都有一个编号，编号从1开始。比如“我爱北京天安门”这个字符串，在常量池中的编号就是7。在字段或者字节码指令中通过编号7可以快速的找到这个字符串</p>
<p>字节码指令中通过编号引用到常量池的过程称之为<strong>符号引用</strong></p>
<h4 id="字段"><a href="#字段" class="headerlink" title="字段"></a>字段</h4><p>字段中存放的是当前类或接口声明的字段信息</p>
<p>如下图中，定义了两个字段a1和a2，这两个字段就会出现在字段这部分内容中。同时还包含字段的名字、描述符（字段的类型）、访问标识（public&#x2F;private static final等）</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/07/pFqqkv9.png"></p>
<h4 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h4><p>字节码中的方法区域是存放<strong>字节码指令</strong>的核心位置，字节码指令的内容存放在方法的Code属性中</p>
<p>通过分析方法的字节码指令，可以清楚地了解一个方法到底是如何执行的。先来看如下案例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i + <span class="number">1</span>;</span><br></pre></td></tr></table></figure>

<p>这段代码编译成字节码指令之后是如下内容：</p>
<p>要理解这段字节码指令是如何执行的，我们需要先理解两块内存区域：操作数栈和局部变量表</p>
<p><strong>操作数栈</strong>是用来存放临时数据的内容，是一个栈式的结构，先进后出</p>
<p><strong>局部变量表</strong>是存放方法中的局部变量，包含方法的参数、方法中定义的局部变量，在编译期就已经可以确定方法有多少个局部变量</p>
<p>1、iconst_0，将常量0放入操作数栈。此时栈上只有0</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/09/pFLbXLR.png"></p>
<p>2、istore_1会从操作数栈中，将栈顶的元素弹出来，此时0会被弹出，放入局部变量表的1号位置。局部变量表中的1号位置，在编译时就已经确定是局部变量i使用的位置。完成了对局部变量i的赋值操作</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/09/pFLbve1.png"></p>
<p>3、iload_1将局部变量表1号位置的数据放入操作数栈中，此时栈中会放入0</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/09/pFLbxdx.png"></p>
<p>4、iconst_1会将常量1放入操作数栈中</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/09/pFLq9JO.png"></p>
<p>5、iadd会将操作数栈顶部的两个数据相加，现在操作数栈上有两个数0和1，相加之后结果为1放入操作数栈中，此时栈上只有一个数也就是相加的结果1</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/09/pFLqiSe.png"></p>
<p>6、istore_2从操作数栈中将1弹出，并放入局部变量表的2号位置，2号位置是j在使用。完成了对局部变量j的赋值操作</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/09/pFLqFQH.png"></p>
<p>7、return语句执行，方法结束并返回</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/09/pFLqkyd.png" alt="img"></p>
<p>同理，可以自行分析下i++和++i的字节码指令执行的步骤</p>
<p>i++的字节码指令如下，其中iinc 1 by 1指令指的是将局部变量表1号位置增加1，其实就实现了i++的操作</p>
<p><img src="" data-original="https://s21.ax1x.com/2024/04/09/pFLqVeI.png"></p>
<p>而++i只是对两个字节码指令的顺序进行了更改：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/09/ngCEIFQd.png" alt="img"></p>
<p><strong>面试题</strong></p>
<p>问：int i &#x3D; 0; i &#x3D; i++; 最终i的值是多少？</p>
<p>答：答案是0，我通过分析字节码指令发现，i++先把0取出来放入临时的操作数栈中，接下来对本地变量表中的i进行加1，本地变量表中的i变成了1，但操作数栈中i任然为0，最后再将之前保存的临时值0放入i，i就变成了0</p>
<h4 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h4><p>属性主要指的是类的属性，比如源码的文件名、内部类的列表等。</p>
<h3 id="字节码文件工具"><a href="#字节码文件工具" class="headerlink" title="字节码文件工具"></a>字节码文件工具</h3><ul>
<li>javap</li>
<li>idea的jclasslib插件</li>
<li>Arthas</li>
<li>dump</li>
<li>jad</li>
</ul>
<h2 id="类的生命周期"><a href="#类的生命周期" class="headerlink" title="类的生命周期"></a>类的生命周期</h2><h3 id="加载阶段"><a href="#加载阶段" class="headerlink" title="加载阶段"></a>加载阶段</h3><p><strong>步骤</strong></p>
<p>1、类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息</p>
<p>渠道如下：</p>
<blockquote>
<ul>
<li>从本地磁盘上获取文件</li>
<li>运行时通过动态代理生成，比如Spring框架</li>
<li>Applet技术通过网络获取字节码文件</li>
</ul>
</blockquote>
<p>2、类加载器加载完类之后，Java虚拟机会将字节码中的信息保存在方法区中，方法区中生成一个InstanceKlass对象，保存类的所有信息，除此之外里面还包含实现特定功能比如多态的信息</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/10/xBXOfVUG.png"></p>
<p>3、Java虚拟机还会再堆中生成一份与方法区中数据类似的java.lang.Class对象。作用是在Java代码中去获取类的信息以及存储静态字段的数据（JDK8及之后）</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/10/O6MMSVNb.png"></p>
<h3 id="连接阶段"><a href="#连接阶段" class="headerlink" title="连接阶段"></a>连接阶段</h3><p>连接阶段分为三个子阶段</p>
<h4 id="校验"><a href="#校验" class="headerlink" title="校验"></a>校验</h4><p>检测Java字节码文件是否满足《Java虚拟机规范》中的约束，一般不需要程序员的参与</p>
<p>主要检验一下四个部分</p>
<p>1、文件格式验证，例如魔数、主次版本号是否满足Java虚拟机版本要求</p>
<p>2、元信息验证，例如类必须有父类</p>
<p>3、验证程序执行指令的语义，例如方法内的指令执行中跳转到不正确的位置</p>
<p>3、符号引用验证，例如是否访问了其他类中private的方法</p>
<p>对版本号的验证，在JDK8的源码中如下：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/11/0nG19dYy.png"></p>
<p>编译文件的主版本号不能高于运行环境主版本号，如果主版本号相等，副版本号也不能超过运行环境副版本号</p>
<h4 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h4><p>该阶段是为静态变量分配内存并设置初值，每一种基本数据类型和引用数据类型都有其初值</p>
<table>
<thead>
<tr>
<th><strong>数据类型</strong></th>
<th><strong>初始值</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>int</strong></td>
<td><strong>0</strong></td>
</tr>
<tr>
<td><strong>long</strong></td>
<td><strong>0L</strong></td>
</tr>
<tr>
<td><strong>short</strong></td>
<td><strong>0</strong></td>
</tr>
<tr>
<td><strong>char</strong></td>
<td><strong>‘\u0000’</strong></td>
</tr>
<tr>
<td><strong>byte</strong></td>
<td><strong>0</strong></td>
</tr>
<tr>
<td><strong>boolean</strong></td>
<td><strong>false</strong></td>
</tr>
<tr>
<td><strong>double</strong></td>
<td><strong>0.0</strong></td>
</tr>
<tr>
<td><strong>引用数据类型</strong></td>
<td><strong>null</strong></td>
</tr>
</tbody></table>
<p>如下面代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span>&#123;</span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在准备阶段会为value分配内存并赋初值为0，<strong>在初始化阶段才会将值修改为1</strong></p>
<p>但如过是下面代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span>&#123;</span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>final修饰的基本数据类型的静态变量，准备阶段直接会将代码中的值进行赋值。在准备阶段value值就直接变成1了，因为final修饰的变量后续不会发生值的变更</p>
<h4 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h4><p>该阶段主要是将常量池中的符号引用替换为<strong>直接引用</strong>，符号引用就是在字节码文件中使用编号来访问常量池中的内容</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/11/PJY8z391.png"></p>
<p>直接引用不再使用编号，而是使用内存中地址进行访问具体的数据</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/11/F24QIDQV.png"></p>
<h3 id="初始化阶段"><a href="#初始化阶段" class="headerlink" title="初始化阶段"></a>初始化阶段</h3><p>该阶段会执行字节码文件中clinit（class init 类的初始化）方法的字节码指令，包含了静态代码块中的代码，并为静态变量赋值</p>
<p>如下面代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo1</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        value = <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">   </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="" data-original="https://img2.imgtp.com/2024/04/11/afialN6l.png"></p>
<ul>
<li>init方法，会在对象初始化时执行</li>
<li>main方法，主方法</li>
<li>clinit方法，类的初始化阶段执行</li>
</ul>
<p>clinit方法中的字节码指令：</p>
<p>1、iconst_1，将常量1放入操作数栈。此时栈中只有1这个数</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/11/EnWPpRNb.png"></p>
<p>2、putstatic指令会将操作数栈上的数弹出来，并放入堆中静态变量的位置，字节码指令中#2指向了常量池中的静态变量value，在<strong>解析阶段</strong>会被替换成变量的地址</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/11/7Yg0XMai.png"></p>
<p>3、后两步操作类似。将2放入操作数栈中，将堆上的value赋值为2</p>
<p>如果将代码的位置互换：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo1</span> &#123;</span><br><span class="line">    <span class="type">static</span> &#123;</span><br><span class="line">        value = <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">   </span><br><span class="line">    <span class="keyword">public</span> <span class="type">static</span> <span class="type">int</span> value = <span class="number">1</span>;</span><br><span class="line">   </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="type">static</span> <span class="type">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>字节码指令的位置也会发生变化：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/11/sPwF1ndJ.png"></p>
<p>这样初始化结束之后，最终value的值就变成了1而不是2</p>
<p>以下几种方式会导致类的初始化：</p>
<ul>
<li>访问一个类的静态变量或者静态方法，<strong>注意变量是final修饰的并且等号右边是常量不会触发初始化</strong></li>
<li>调用Class.forName(String className)</li>
<li>new一个该类的对象时</li>
<li>执行Main方法的当前类</li>
</ul>
<h4 id="面试题1"><a href="#面试题1" class="headerlink" title="面试题1"></a>面试题1</h4><p>如下代码的输出结果是什么？</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;A&quot;</span>);</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Test1</span>();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Test1</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Test1</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;B&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;C&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;D&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最后的结果应该是DACBCB</p>
<h4 id="clinit不会执行的几种情况"><a href="#clinit不会执行的几种情况" class="headerlink" title="clinit不会执行的几种情况"></a>clinit不会执行的几种情况</h4><p>如下几种情况是不会进行初始化指令执行的：</p>
<p>1.无静态代码块且无静态变量赋值语句</p>
<p>2.有静态变量的声明，但是没有赋值语句</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> a;</span><br></pre></td></tr></table></figure>

<p>3.静态变量的定义使用final关键字，这类变量会在准备阶段直接进行初始化</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">10</span>;</span><br></pre></td></tr></table></figure>

<h4 id="面试题2"><a href="#面试题2" class="headerlink" title="面试题2"></a>面试题2</h4><p>如下代码的输出结果是什么？</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo01</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">B02</span>();</span><br><span class="line">        System.out.println(B02.a);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A02</span>&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        a = <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B02</span> <span class="keyword">extends</span> <span class="title class_">A02</span>&#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        a = <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>分析步骤：</p>
<p>1、调用new创建对象，需要初始化B02，优先初始化父类</p>
<p>2、执行A02的初始化代码，将a赋值为1</p>
<p>3、B02初始化，将a赋值为2</p>
<p><strong>变化</strong></p>
<p>将<code>new B02();</code>注释掉会怎么样？</p>
<p>分析步骤：</p>
<p>1、访问父类的静态变量，只初始化父类</p>
<p>2、执行A02的初始化代码，将a赋值为1</p>
<h4 id="补充练习题"><a href="#补充练习题" class="headerlink" title="补充练习题"></a>补充练习题</h4><p>分析如下代码执行结果:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        Test2_A[] arr = <span class="keyword">new</span> <span class="title class_">Test2_A</span>[<span class="number">10</span>];</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Test2_A</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Test2 A的静态代码块运行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>数组的创建不会导致数组中元素的类进行初始化</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test4</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(Test4_A.a);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Test4_A</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> Integer.valueOf(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Test3 A的静态代码块运行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>final修饰的变量如果赋值的内容需要执行指令才能得出结果，会执行clinit方法进行初始化</p>
<h2 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h2><h3 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h3><p>类加载器（ClassLoader）是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术，类加载器只参与加载过程中的字节码获取并加载到内存这一部分</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/11/QAWzQUe8.png"></p>
<p>类加载器会通过二进制流的方式获取到字节码文件的内容，接下来将获取到的数据交给Java虚拟机，虚拟机会在方法区和堆上生成对应的对象保存字节码信息</p>
<h3 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h3><p>类加载器分为两类，一类是Java代码中实现的，一类是Java虚拟机底层源码实现的</p>
<ul>
<li>虚拟机底层实现：源代码位于Java虚拟机的源码中，实现语言与虚拟机底层语言一致，比如Hotspot使用C++。主要目的是保证Java程序运行中基础类被正确地加载，比如java.lang.String，Java虚拟机需要确保其可靠性。</li>
<li>JDK中默认提供或者自定义：JDK中默认提供了多种处理不同渠道的类加载器，程序员也可以自己根据需求定制，使用Java语言。所有Java中实现的类加载器都需要继承ClassLoader这个抽象类。</li>
</ul>
<p>类加载器的设计JDK8和8之后的版本差别较大，首先来看JDK8及之前的版本，这些版本中默认的类加载器有如下几种：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/VHJeDv0S.png"></p>
<p>类加载器的详细信息可以通过Arthas的classloader命令查看</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/29PRcA0S.png"></p>
<ul>
<li>BootstrapClassLoader是启动类加载器，numberOfInstances是类加载器的数量只有1个，loadedCountTotal是加载类的数量1861个。</li>
<li>ExtClassLoader是扩展类加载器</li>
<li>AppClassLoader是应用程序类加载器</li>
</ul>
<h3 id="启动类加载器"><a href="#启动类加载器" class="headerlink" title="启动类加载器"></a>启动类加载器</h3><ul>
<li>启动类加载器（Bootstrap ClassLoader）是由Hotspot虚拟机提供的、使用C++编写的类加载器。</li>
<li>默认加载Java安装目录&#x2F;jre&#x2F;lib下的类文件，比如rt.jar，tools.jar，resources.jar等。</li>
</ul>
<p>运行如下代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 启动程序类加载器案例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BootstrapClassLoaderDemo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">ClassLoader</span> <span class="variable">classLoader</span> <span class="operator">=</span> String.class.getClassLoader();</span><br><span class="line">        System.out.println(classLoader);</span><br><span class="line"></span><br><span class="line">        System.in.read();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这段代码通过String类获取到它的类加载器并且打印，结果是<code>null</code>。这是因为启动类加载器在JDK8中是由C++语言来编写的，在Java代码中去获取既不适合也不安全，所以才返回<code>null</code></p>
<p>在Arthas中可以通过<code>sc -d 类名</code>的方式查看加载这个类的类加载器详细的信息</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/aHM1lSAR.png"></p>
<p>通过上图可以看到，java.lang.String类的类加载器是空的，Hash值也是null</p>
<h4 id="用户扩展基础jar包"><a href="#用户扩展基础jar包" class="headerlink" title="用户扩展基础jar包"></a>用户扩展基础jar包</h4><p>如果用户想扩展一些比较基础的jar包，让启动类加载器加载，有两种途径：</p>
<ul>
<li><strong>放入jre&#x2F;lib下进行扩展</strong>。不推荐，尽可能不要去更改JDK安装目录中的内容，会出现即时放进去由于文件名不匹配的问题也不会正常地被加载</li>
<li><strong>使用参数进行扩展。</strong>推荐，在IDEA中JVM参数使用-Xbootclasspath&#x2F;a:jar包目录&#x2F;jar包名 进行扩展，参数中的&#x2F;a代表新增</li>
</ul>
<h3 id="扩展类加载器和应用程序类加载器"><a href="#扩展类加载器和应用程序类加载器" class="headerlink" title="扩展类加载器和应用程序类加载器"></a>扩展类加载器和应用程序类加载器</h3><ul>
<li>扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器</li>
<li>它们的源码都位于sun.misc.Launcher中，是一个静态内部类。继承自URLClassLoader。具备通过目录或者指定jar包将字节码文件加载到内存中</li>
</ul>
<p>继承关系图如下：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/yPyXcuWO.png"></p>
<ul>
<li>ClassLoader类定义了具体的行为模式，简单来说就是先从本地或者网络获得字节码信息，然后调用虚拟机底层的方法创建方法区和堆上的对象。这样的好处就是让子类只需要去实现如何获取字节码信息这部分代码</li>
<li>SecureClassLoader提供了证书机制，提升了安全性</li>
<li>URLClassLoader提供了根据URL获取目录下或者指定jar包进行加载，获取字节码的数据</li>
<li>扩展类加载器和应用程序类加载器继承自URLClassLoader，获得了上述的三种能力</li>
</ul>
<h4 id="拓展类加载器"><a href="#拓展类加载器" class="headerlink" title="拓展类加载器"></a>拓展类加载器</h4><p>扩展类加载器（Extension Class Loader）是JDK中提供的、使用Java编写的类加载器。默认加载Java安装目录&#x2F;jre&#x2F;lib&#x2F;ext下的类文件</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/Kl4zAhO6.png"></p>
<p>如下代码会打印ScriptEnvironment类的类加载器。ScriptEnvironment是nashorn.jar中用来运行javascript语言代码的环境类，他位于nashorn.jar包中被扩展类加载器加载</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 扩展类加载器</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ExtClassLoaderDemo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">ClassLoader</span> <span class="variable">classLoader</span> <span class="operator">=</span> ScriptEnvironment.class.getClassLoader();</span><br><span class="line">        System.out.println(classLoader);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>打印结果如下：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/cCbcsZe7.png"></p>
<p>通过扩展类加载器去加载用户jar包：</p>
<ul>
<li><strong>放入&#x2F;jre&#x2F;lib&#x2F;ext下进行扩展</strong>。不推荐，尽可能不要去更改JDK安装目录中的内容。</li>
<li><strong>使用参数进行扩展使用参数进行扩展</strong>。推荐，使用IDEA中jvm参数-Djava.ext.dirs&#x3D;jar包目录进行扩展,这种方式会覆盖掉原始目录，可以用;(windows):(macos&#x2F;linux)追加上原始目录</li>
</ul>
<p>使用<code>引号</code>将整个地址包裹起来，这样路径中即便是有空格也不需要额外处理。路径中要包含原来ext文件夹，同时在最后加上扩展的路径</p>
<h4 id="应用加载器"><a href="#应用加载器" class="headerlink" title="应用加载器"></a>应用加载器</h4><p>应用程序类加载器会加载classpath下的类文件，默认加载的是项目中的类以及通过maven引入的第三方jar包中的类。</p>
<p>如下案例中，打印出<code>Student</code>和<code>FileUtils</code>的类加载器：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 应用程序类加载器案例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AppClassLoaderDemo</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="type">static</span> <span class="type">void</span> <span class="title">main</span><span class="params">(String[] args)</span> throws IOException, InterruptedException </span>&#123;</span><br><span class="line">        <span class="comment">//当前项目中创建的Student类</span></span><br><span class="line">        Student student = <span class="keyword">new</span> <span class="built_in">Student</span>();</span><br><span class="line">        ClassLoader classLoader = Student.<span class="keyword">class</span>.<span class="built_in">getClassLoader</span>();</span><br><span class="line">        System.out.<span class="built_in">println</span>(classLoader);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//maven依赖中包含的类</span></span><br><span class="line">        ClassLoader classLoader1 = FileUtils.<span class="keyword">class</span>.<span class="built_in">getClassLoader</span>();</span><br><span class="line">        System.out.<span class="built_in">println</span>(classLoader1);</span><br><span class="line"></span><br><span class="line">        Thread.<span class="built_in">sleep</span>(<span class="number">1000</span>);</span><br><span class="line">        System.in.<span class="built_in">read</span>();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出结果如下：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/2LKUOLYp.png"></p>
<p>这两个类均由应用程序类加载器加载。</p>
<p>类加载器的加载路径可以通过classloader –c hash值 查看：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/mg550UAs.png"></p>
<h3 id="JDK9之后的类加载器"><a href="#JDK9之后的类加载器" class="headerlink" title="JDK9之后的类加载器"></a>JDK9之后的类加载器</h3><p>JDK8及之前的版本中，扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java</p>
<p>由于JDK9引入了module的概念，类加载器在设计上发生了很多变化：</p>
<ul>
<li><p>启动类加载器使用Java编写，位于jdk.internal.loader.ClassLoaders类中</p>
</li>
<li><p>Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件</p>
</li>
<li><p>启动类加载器依然无法通过java代码获取到，返回的仍然是null，保持了统一</p>
</li>
<li><p>扩展类加载器被替换成了平台类加载器（Platform Class Loader）</p>
</li>
<li><p>平台类加载器遵循模块化方式加载字节码文件，所以继承关系从URLClassLoader变成了BuiltinClassLoader，BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是为了与老版本的设计方案兼容，自身没有特殊的逻辑。</p>
</li>
</ul>
<h2 id="双亲委派机制"><a href="#双亲委派机制" class="headerlink" title="双亲委派机制"></a>双亲委派机制</h2><p>由于Java虚拟机中存在多个类加载器，双亲委派机制核心就是<strong>解决一个类到底是谁加载的问题</strong></p>
<p>双亲委派机制指的是：当一个类加载器接收到加载类的任务时，会自底向上查找是否加载过，再由顶向下进行加载</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/ldYngUvo.png"></p>
<p>详细流程：</p>
<p>每个类加载器都有一个父类加载器。父类加载器的关系如下，启动类加载器没有父类加载器：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/dWYQDiNQ.png"></p>
<p>在类加载的过程中，每个类加载器都会先检查是否已经加载了该类，如果已经加载则直接返回，否则会将加载请求委派给父类加载器</p>
<p>###案例1</p>
<p>比如com.itheima.my.A假设在启动类加载器的加载目录中，而应用程序类加载器接到了加载类的任务</p>
<p>1、应用程序类加载器首先判断自己加载过没有，没有加载过就交给父类加载器 - 扩展类加载器</p>
<p>2、扩展类加载器也没加载过，交给他的父类加载器 - 启动类加载器</p>
<p>3、启动类加载器发现已经加载过，直接返回</p>
<h3 id="案例2"><a href="#案例2" class="headerlink" title="案例2"></a>案例2</h3><p>比如com.itheima.my.B类在扩展类加载器加载路径中，同样应用程序类加载器接到了加载任务，</p>
<p>1、按照案例1中的方式一层一层向上查找，发现都没有加载过</p>
<p>2、那么启动类加载器会首先尝试加载。它发现这类不在它的加载目录中，向下传递给扩展类加载器</p>
<p>3、扩展类加载器发现这个类在它加载路径中，加载成功并返回</p>
<p>如果第二次再接收到加载任务，同样地向上查找。扩展类加载器发现已经加载过，就可以返回了</p>
<h3 id="双亲委派机制的作用"><a href="#双亲委派机制的作用" class="headerlink" title="双亲委派机制的作用"></a>双亲委派机制的作用</h3><p>1.保证类加载的安全性。通过双亲委派机制避免恶意代码替换JDK中的核心类库</p>
<p>比如我们自己创建的一个报名为java.lang.String，现在打印出这个类加载器，按道理应该是在应用加载器被加载，但是却出现<code>null</code>，说明是在启动加载器中被加载</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/VFRXVcdF.png"></p>
<p>2.避免重复加载。双亲委派机制可以避免同一个类被多次加载。</p>
<h3 id="如何指定加载类的类加载器？"><a href="#如何指定加载类的类加载器？" class="headerlink" title="如何指定加载类的类加载器？"></a>如何指定加载类的类加载器？</h3><p>在Java中如何使用代码的方式去主动加载一个类呢？</p>
<p>方式1：使用Class.forName方法，使用当前类的类加载器去加载指定的类。</p>
<p>方式2：获取到类加载器，通过类加载器的loadClass方法指定某个类加载器加载。</p>
<p>例如：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/14/l34SLKr8.png"></p>
<h3 id="面试题"><a href="#面试题" class="headerlink" title="面试题"></a>面试题</h3><p>1、如果一个类重复出现在三个类加载器的加载位置，应该由谁来加载？</p>
<p>启动类加载器加载，根据双亲委派机制，它的优先级是最高的</p>
<p>2、String类能覆盖吗，在自己的项目中去创建一个java.lang.String类，会被加载吗？</p>
<p>不能，会返回启动类加载器加载在rt.jar包中的String类</p>
<p>3、<strong>类的双亲委派机制是什么？（重点）</strong></p>
<ul>
<li>当一个类加载器去加载某个类的时候，会自底向上查找是否加载过，如果加载过就直接返回，如果一直到最顶层的类加载器都没有加载，再由顶向下进行加载。</li>
<li>应用程序类加载器的父类加载器是扩展类加载器，扩展类加载器的父类加载器是启动类加载器。</li>
<li>双亲委派机制的好处有两点：第一是避免恶意代码替换JDK中的核心类库，比如java.lang.String，确保核心类库的完整性和安全性。第二是避免一个类重复地被加载。</li>
</ul>
<h2 id="打破双亲委派机制"><a href="#打破双亲委派机制" class="headerlink" title="打破双亲委派机制"></a>打破双亲委派机制</h2><p>打破双亲委派机制历史上有三种方式，但本质上只有第一种算是真正的打破了双亲委派机制：</p>
<ul>
<li>自定义类加载器并且重写loadClass方法。Tomcat通过这种方式实现应用之间类隔离，《面试篇》中分享它的做法</li>
<li>线程上下文类加载器。利用上下文类加载器加载类，比如JDBC和JNDI等</li>
<li>Osgi框架的类加载器。历史上Osgi框架实现了一套新的类加载器机制，允许同级之间委托进行类的加载，目前很少使用</li>
</ul>
<h3 id="自定义类加载器"><a href="#自定义类加载器" class="headerlink" title="自定义类加载器"></a>自定义类加载器</h3><p>一个Tomcat程序中是可以运行多个Web应用的，如果这两个应用中出现了相同限定名的类，比如Servlet类，Tomcat要保证这两个类都能加载并且它们应该是不同的类。如果不打破双亲委派机制，当应用类加载器加载Web应用1中的MyServlet之后，Web应用2中相同限定名的MyServlet类就无法被加载了。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/iws9lcsO.png"></p>
<p>Tomcat使用了自定义类加载器来实现应用之间类的隔离。 每一个应用会有一个独立的类加载器加载对应的类</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/G5YWnHSi.png"></p>
<h4 id="双亲委派机制流程"><a href="#双亲委派机制流程" class="headerlink" title="双亲委派机制流程"></a>双亲委派机制流程</h4><p>那么自定义加载器是如何能做到的呢？首先我们需要先了解，双亲委派机制的代码到底在哪里，接下来只需要把这段代码消除即可</p>
<p>ClassLoader中包含了4个核心方法，双亲委派机制的核心代码就位于loadClass方法中</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Class&lt;?&gt; loadClass(String name)</span><br><span class="line"><span class="comment">// 类加载的入口，提供了双亲委派机制。内部会调用findClass   重要</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> Class&lt;?&gt; findClass(String name)</span><br><span class="line"><span class="comment">// 由类加载器子类实现,获取二进制数据调用defineClass ，比如URLClassLoader会根据文件路径去获取类文件中的二进制数据。重要</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> Class&lt;?&gt; defineClass(String name, <span class="type">byte</span>[] b, <span class="type">int</span> off, <span class="type">int</span> len)</span><br><span class="line"><span class="comment">// 做一些类名的校验，然后调用虚拟机底层的方法将字节码信息加载到虚拟机内存中</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">resolveClass</span><span class="params">(Class&lt;?&gt; c)</span></span><br><span class="line"><span class="comment">// 执行类生命周期中的连接阶段，默认为false</span></span><br></pre></td></tr></table></figure>

<p>1、入口方法：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/AU0qsKTr.png"></p>
<p>2、再进入看下：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/9rNGFWYc.png"></p>
<p>如果查找都失败，进入加载阶段，首先会由启动类加载器加载，这段代码在<code>findBootstrapClassOrNull</code>中。如果失败会抛出异常，接下来执行下面这段代码：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/FJKDbsRw.png"></p>
<p>父类加载器加载失败就会抛出异常，回到子类加载器的这段代码，这样就实现了加载并向下传递</p>
<p>3、最后根据传入的参数判断是否进入连接阶段</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/yj0Kiy2R.png"></p>
<h4 id="自定义代码打破双亲委派机制"><a href="#自定义代码打破双亲委派机制" class="headerlink" title="自定义代码打破双亲委派机制"></a>自定义代码打破双亲委派机制</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> classloader.broken;<span class="comment">//package com.itheima.jvm.chapter02.classloader.broken;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.commons.io.IOUtils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.io.InputStream;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.StandardCharsets;</span><br><span class="line"><span class="keyword">import</span> java.security.ProtectionDomain;</span><br><span class="line"><span class="keyword">import</span> java.util.regex.Matcher;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 打破双亲委派机制 - 自定义类加载器</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BreakClassLoader1</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String basePath;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">String</span> <span class="variable">FILE_EXT</span> <span class="operator">=</span> <span class="string">&quot;.class&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//设置加载目录</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setBasePath</span><span class="params">(String basePath)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.basePath = basePath;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//使用commons io 从指定目录下加载文件</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">byte</span>[] loadClassData(String name)  &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">tempName</span> <span class="operator">=</span> name.replaceAll(<span class="string">&quot;\\.&quot;</span>, Matcher.quoteReplacement(File.separator));</span><br><span class="line">            <span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(basePath + tempName + FILE_EXT);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> IOUtils.toByteArray(fis);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                IOUtils.closeQuietly(fis);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;自定义类加载器加载失败，错误原因：&quot;</span> + e.getMessage());</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//重写loadClass方法</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Class&lt;?&gt; loadClass(String name) <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">        <span class="comment">//如果是java包下，还是走双亲委派机制</span></span><br><span class="line">        <span class="keyword">if</span>(name.startsWith(<span class="string">&quot;java.&quot;</span>))&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">super</span>.loadClass(name);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//从磁盘中指定目录下加载</span></span><br><span class="line">        <span class="type">byte</span>[] data = loadClassData(name);</span><br><span class="line">        <span class="comment">//调用虚拟机底层方法，方法区和堆区创建对象</span></span><br><span class="line">        <span class="keyword">return</span> defineClass(name, data, <span class="number">0</span>, data.length);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ClassNotFoundException, InstantiationException, IllegalAccessException, IOException &#123;</span><br><span class="line">        <span class="comment">//第一个自定义类加载器对象</span></span><br><span class="line">        <span class="type">BreakClassLoader1</span> <span class="variable">classLoader1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BreakClassLoader1</span>();</span><br><span class="line">        classLoader1.setBasePath(<span class="string">&quot;D:\\lib\\&quot;</span>);</span><br><span class="line"></span><br><span class="line">        Class&lt;?&gt; clazz1 = classLoader1.loadClass(<span class="string">&quot;com.itheima.my.A&quot;</span>);</span><br><span class="line">         <span class="comment">//第二个自定义类加载器对象</span></span><br><span class="line">        <span class="type">BreakClassLoader1</span> <span class="variable">classLoader2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BreakClassLoader1</span>();</span><br><span class="line">        classLoader2.setBasePath(<span class="string">&quot;D:\\lib\\&quot;</span>);</span><br><span class="line"></span><br><span class="line">        Class&lt;?&gt; clazz2 = classLoader2.loadClass(<span class="string">&quot;com.itheima.my.A&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// false，说明两个类加载器不一致</span></span><br><span class="line">        System.out.println(clazz1 == clazz2);</span><br><span class="line"></span><br><span class="line">        Thread.currentThread().setContextClassLoader(classLoader1);</span><br><span class="line"></span><br><span class="line">        System.out.println(Thread.currentThread().getContextClassLoader());</span><br><span class="line"></span><br><span class="line">        System.in.read();</span><br><span class="line">     &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="自定义类加载器父类怎么是AppClassLoader呢？"><a href="#自定义类加载器父类怎么是AppClassLoader呢？" class="headerlink" title="自定义类加载器父类怎么是AppClassLoader呢？"></a>自定义类加载器父类怎么是AppClassLoader呢？</h4><p>默认情况下自定义类加载器的父类加载器是<strong>应用程序类加载器</strong>：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/caYeXgD3.png"></p>
<p>以Jdk8为例，ClassLoader类中提供了构造方法设置parent的内容：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/EgpSOYFI.png"></p>
<p>这个构造方法由另外一个构造方法调用，其中父类加载器由getSystemClassLoader方法设置，该方法返回的是AppClassLoader</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/xcMmAJMI.png"></p>
<h4 id="两个自定义类加载器加载相同限定名的类，不会冲突吗？"><a href="#两个自定义类加载器加载相同限定名的类，不会冲突吗？" class="headerlink" title="两个自定义类加载器加载相同限定名的类，不会冲突吗？"></a>两个自定义类加载器加载相同限定名的类，不会冲突吗？</h4><p><strong>不会冲突</strong>，在同一个Java虚拟机中，只有相同类加载器+相同的类限定名才会被认为是同一个类。</p>
<p>在Arthas中使用sc –d 类名的方式查看具体的情况。</p>
<p>如下代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ClassNotFoundException, InstantiationException, IllegalAccessException, IOException &#123;</span><br><span class="line">       <span class="comment">//第一个自定义类加载器对象</span></span><br><span class="line">       <span class="type">BreakClassLoader1</span> <span class="variable">classLoader1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BreakClassLoader1</span>();</span><br><span class="line">       classLoader1.setBasePath(<span class="string">&quot;D:\\lib\\&quot;</span>);</span><br><span class="line"></span><br><span class="line">       Class&lt;?&gt; clazz1 = classLoader1.loadClass(<span class="string">&quot;com.itheima.my.A&quot;</span>);</span><br><span class="line">        <span class="comment">//第二个自定义类加载器对象</span></span><br><span class="line">       <span class="type">BreakClassLoader1</span> <span class="variable">classLoader2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BreakClassLoader1</span>();</span><br><span class="line">       classLoader2.setBasePath(<span class="string">&quot;D:\\lib\\&quot;</span>);</span><br><span class="line"></span><br><span class="line">       Class&lt;?&gt; clazz2 = classLoader2.loadClass(<span class="string">&quot;com.itheima.my.A&quot;</span>);</span><br><span class="line"></span><br><span class="line">    	<span class="comment">// false，说明两个类加载器不一致</span></span><br><span class="line">       System.out.println(clazz1 == clazz2);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>打印的应该是false，因为两个类加载器不同，尽管加载的是同一个类名，最终Class对象也不是相同的</p>
<h3 id="线程上下文类加载器"><a href="#线程上下文类加载器" class="headerlink" title="线程上下文类加载器"></a>线程上下文类加载器</h3><p>利用上下文类加载器加载类，比如JDBC和JNDI等。</p>
<p>我们来看下JDBC的案例：</p>
<p>1、JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动，比如mysql驱动、oracle驱动。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> classloader.broken;<span class="comment">//package com.itheima.jvm.chapter02.classloader.broken;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.mysql.cj.jdbc.Driver;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.sql.*;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 打破双亲委派机制 - JDBC案例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JDBCExample</span> &#123;</span><br><span class="line">    <span class="comment">// JDBC driver name and database URL</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">JDBC_DRIVER</span> <span class="operator">=</span> <span class="string">&quot;com.mysql.cj.jdbc.Driver&quot;</span>;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">DB_URL</span> <span class="operator">=</span> <span class="string">&quot;jdbc:mysql:///bank1&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//  Database credentials</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">USER</span> <span class="operator">=</span> <span class="string">&quot;root&quot;</span>;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">PASS</span> <span class="operator">=</span> <span class="string">&quot;123456&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">Statement</span> <span class="variable">stmt</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            conn = DriverManager.getConnection(DB_URL, USER, PASS);</span><br><span class="line">            stmt = conn.createStatement();</span><br><span class="line">            String sql;</span><br><span class="line">            sql = <span class="string">&quot;SELECT id, account_name FROM account_info&quot;</span>;</span><br><span class="line">            <span class="type">ResultSet</span> <span class="variable">rs</span> <span class="operator">=</span> stmt.executeQuery(sql);</span><br><span class="line"></span><br><span class="line">            <span class="comment">//STEP 4: Extract data from result set</span></span><br><span class="line">            <span class="keyword">while</span> (rs.next()) &#123;</span><br><span class="line">                <span class="comment">//Retrieve by column name</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">id</span> <span class="operator">=</span> rs.getInt(<span class="string">&quot;id&quot;</span>);</span><br><span class="line">                <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> rs.getString(<span class="string">&quot;account_name&quot;</span>);</span><br><span class="line"></span><br><span class="line">                <span class="comment">//Display values</span></span><br><span class="line">                System.out.print(<span class="string">&quot;ID: &quot;</span> + id);</span><br><span class="line">                System.out.print(<span class="string">&quot;, Name: &quot;</span> + name + <span class="string">&quot;\n&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//STEP 5: Clean-up environment</span></span><br><span class="line">            rs.close();</span><br><span class="line">            stmt.close();</span><br><span class="line">            conn.close();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SQLException se) &#123;</span><br><span class="line">            <span class="comment">//Handle errors for JDBC</span></span><br><span class="line">            se.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">//Handle errors for Class.forName</span></span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">//finally block used to close resources</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (stmt != <span class="literal">null</span>)</span><br><span class="line">                    stmt.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (SQLException se2) &#123;</span><br><span class="line">            &#125;<span class="comment">// nothing we can do</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (conn != <span class="literal">null</span>)</span><br><span class="line">                    conn.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (SQLException se) &#123;</span><br><span class="line">                se.printStackTrace();</span><br><span class="line">            &#125;<span class="comment">//end finally try</span></span><br><span class="line">        &#125;<span class="comment">//end try</span></span><br><span class="line">    &#125;<span class="comment">//end main</span></span><br><span class="line">&#125;<span class="comment">//end FirstExample</span></span><br></pre></td></tr></table></figure>

<p>2、DriverManager类位于rt.jar包中，由启动类加载器加载。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/xlYbm7m5.png"></p>
<p>3、依赖中的mysql驱动对应的类，由应用程序类加载器来加载。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/6NYAKXJn.png"></p>
<p>在类中有初始化代码：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/nbLvJ6mw.png"></p>
<p>DriverManager属于rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载，<em>这就违反了双亲委派机制</em>。（这点存疑，一会儿再讨论）</p>
<p>那么问题来了，DriverManager怎么知道jar包中要加载的驱动在哪儿？</p>
<p>1、在类的初始化代码中有这么一个方法<code>LoadInitialDrivers</code>：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/AcvoSlqy.png"></p>
<p>2、这里使用了SPI机制，去加载所有jar包中实现了Driver接口的实现类。<img src="" data-original="https://img2.imgtp.com/2024/04/15/JtDvuqXB.png"></p>
<p>3、SPI机制就是在这个位置下存放了一个文件，文件名是接口名，文件里包含了实现类的类名。这样SPI机制就可以找到实现类了。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/q9cEbbln.png"></p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/uTkk9Rpe.png"></p>
<p>4、SPI中利用了线程上下文类加载器（应用程序类加载器）去加载类并创建对象。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/a5Ov7EQn.png"></p>
<p>总结：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/SGf8ibgn.png"></p>
<h4 id="JDBC案例中真的打破了双亲委派机制吗？"><a href="#JDBC案例中真的打破了双亲委派机制吗？" class="headerlink" title="JDBC案例中真的打破了双亲委派机制吗？"></a>JDBC案例中真的打破了双亲委派机制吗？</h4><p>最早这个论点提出是在周志明《深入理解Java虚拟机》中，他认为打破了双亲委派机制，这种由启动类加载器加载的类，委派应用程序类加载器去加载类的方式，所以打破了双亲委派机制。</p>
<p>但是如果我们分别从DriverManager以及驱动类的加载流程上分析，JDBC只是在DriverManager加载完之后，通过初始化阶段触发了驱动类的加载，类的加载依然遵循双亲委派机制。</p>
<p>所以我认为这里没有打破双亲委派机制，只是用一种巧妙的方法让启动类加载器加载的类，去引发的其他类的加载。</p>
<h3 id="Osgi框架的类加载器"><a href="#Osgi框架的类加载器" class="headerlink" title="Osgi框架的类加载器"></a>Osgi框架的类加载器</h3><p>历史上，OSGi模块化框架。它存在同级之间的类加载器的委托加载。OSGi还使用类加载器实现了热部署的功能。热部署指的是在服务不停止的情况下，动态地更新字节码文件到内存中。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/15/vNWsddUN.png"></p>
<p>由于这种机制使用已经不多，所以不再过多讨论OSGi</p>
<h2 id="运行时数据区"><a href="#运行时数据区" class="headerlink" title="运行时数据区"></a>运行时数据区</h2><p>Java虚拟机在运行Java程序过程中管理的内存区域，称之为运行时数据区。《Java虚拟机规范》中规定了每一部分的作用</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/Qy1tgBmg.png"></p>
<h3 id="程序计数器"><a href="#程序计数器" class="headerlink" title="程序计数器"></a>程序计数器</h3><p>程序计数器（Program Counter Register）也叫PC寄存器，每个线程会通过程序计数器记录当前要执行的的字节码指令的地址。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/npojeY27.png"></p>
<p>一个程序计数器的具体案例：</p>
<p>在加载阶段，虚拟机将字节码文件中的指令读取到内存之后，会将原文件中的偏移量转换成内存地址。每一条字节码指令都会拥有一个内存地址。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/7bLTNK5f.png"></p>
<p>在代码执行过程中，程序计数器会记录下一行字节码指令的地址。执行完当前指令之后，虚拟机的执行引擎根据程序计数器执行下一行指令。这里为了简单起见，使用偏移量代替，真实内存中执行时保存的应该是地址。</p>
<p>比如当前执行的是偏移量为0的指令，那么程序计数器中保存的就是下一条的地址（偏移量1）。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/CAp4q7EY.png"></p>
<p>一路向下执行</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/ogQf9fNE.png"></p>
<p>一直执行到方法的最后一行指令，此时方法执行return语句，当前方法执行结束，程序计数器中会放入方法出口的地址（栈中讲解，简单来说就是这个B方法结束了，A调用了B，那么要回到A方法）</p>
<p>所以，程序计数器可以控制程序指令的进行，实现分支、跳转、异常等逻辑。不管是分支、跳转、异常，只需要在程序计数器中放入下一行要执行的指令地址即可。</p>
<p>在多线程执行情况下，Java虚拟机需要通过程序计数器记录CPU切换前解释执行到那一句指令并继续解释运行。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/ZiKzOaJo.png"></p>
<h4 id="程序计数器会出现内存溢出吗？"><a href="#程序计数器会出现内存溢出吗？" class="headerlink" title="程序计数器会出现内存溢出吗？"></a>程序计数器会出现内存溢出吗？</h4><p>内存溢出指的是程序在使用某一块内存区域时，存放的数据需要占用的内存大小超过了虚拟机能提供的内存上限。由于每个线程只存储一个固定长度的内存地址，程序计数器是不会发生内存溢出的。程序员无需对程序计数器做任何处理。</p>
<h3 id="Java虚拟机栈"><a href="#Java虚拟机栈" class="headerlink" title="Java虚拟机栈"></a>Java虚拟机栈</h3><p>Java虚拟机栈（Java Virtual Machine Stack）采用栈的数据结构来管理方法调用中的基本数据，先进后出（First In Last Out）,每一个方法的调用使用一个栈帧（Stack Frame）来保存。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MethodDemo</span> &#123;   </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;        </span><br><span class="line">         study();    </span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">study</span><span class="params">()</span>&#123;</span><br><span class="line">        eat();</span><br><span class="line"></span><br><span class="line">        sleep();</span><br><span class="line">    &#125;   </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">eat</span><span class="params">()</span>&#123;       </span><br><span class="line">         System.out.println(<span class="string">&quot;吃饭&quot;</span>);   </span><br><span class="line">    &#125;    </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">sleep</span><span class="params">()</span>&#123;        </span><br><span class="line">        System.out.println(<span class="string">&quot;睡觉&quot;</span>);    </span><br><span class="line">        &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>main方法执行时，会创建main方法的栈帧</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/ltzZgIcS.png"></p>
<p>接下来执行study方法，会创建study方法的栈帧</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/9pwcr1jJ.png"></p>
<p>进入eat方法，创建eat方法的栈帧</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/GNyyzaFX.png"></p>
<p>eat方法执行完之后，会弹出它的栈帧</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/1myIfdyj.png"></p>
<p>然后调用sleep方法，创建sleep方法栈帧</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/htjKWZX5.png"></p>
<p>最后study方法结束之后弹出栈帧，main方法结束之后弹出main的栈帧</p>
<p>Java虚拟机栈随着线程的创建而创建，而回收则会在线程的销毁时进行。由于方法可能会在不同线程中执行，每个线程都会包含一个自己的虚拟机栈。如下就有两个线程的虚拟机栈，main线程和线程A。<img src="" data-original="https://img2.imgtp.com/2024/04/22/RiyEiKG0.png"></p>
<p>Java虚拟机栈的栈帧中主要包含三方面的内容：</p>
<ul>
<li><strong>局部变量表</strong>，局部变量表的作用是在运行过程中存放所有的局部变量</li>
<li><strong>操作数栈</strong>，操作数栈是栈帧中虚拟机在执行指令过程中用来存放临时数据的一块区域</li>
<li><strong>帧数据</strong>，帧数据主要包含动态链接、方法出口、异常表的引用</li>
</ul>
<h4 id="局部变量表"><a href="#局部变量表" class="headerlink" title="局部变量表"></a>局部变量表</h4><p>局部变量表的作用是在方法执行过程中存放所有的局部变量。局部变量表分为两种，一种是字节码文件中的，另外一种是栈帧中的也就是保存在内存中。栈帧中的局部变量表是根据字节码文件中的内容生成的。</p>
<p>我们先来看下<strong>字节码文件中的局部变量表</strong>：编译成字节码文件时就可以确定局部变量表的内容。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test1</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">long</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>test1方法的局部变量表如下：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/tthBRHHc.png"></p>
<p>局部变量表中保存了字节码指令生效的偏移量：<img src="" data-original="https://img2.imgtp.com/2024/04/22/AqgGTjRE.png"></p>
<p>比如<code>i</code>这个变量，它的起始PC是2，代表从<code>lconst_1</code>这句指令开始才能使用<code>i</code>，长度为3，也就是2-4这三句指令都可以使用<code>i</code>。为什么从2才能使用，因为0和1这两句字节码指令还在处理<code>int i = 0</code>这句赋值语句。<code>j</code>这个变量只有等3指令执行完之后也就是<code>long j = 1</code>代码执行完之后才能使用，所以起始PC为4，只能在4这行字节码指令中使用。</p>
<p>接下来看下<strong>栈帧中的局部变量表</strong>，栈帧中的局部变量表是一个数组，数组中每一个位置称之为槽(slot) ，long和double类型占用两个槽，其他类型占用一个槽。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/BhLb6i2V.png"></p>
<p><code>i</code>占用数组下标为0的位置，<code>j</code>占用数组下标1-2的位置。</p>
<p>刚才看到的是静态方法，实例方法中的序号为0的位置存放的是this，指的是当前调用方法的对象，运行时会在内存中存放实例对象的地址。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/8qUAuqZD.png"></p>
<p>方法参数也会保存在局部变量表中，其顺序与方法中参数定义的顺序一致。局部变量表保存的内容有：<strong>实例方法的this对象，方法的参数，方法体中声明的局部变量</strong>。test3方法中包含两个参数<code>k</code>,<code>m</code>，这两个参数也会被加入到局部变量表中。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/22/CCXFjhUr.png"></p>
<p><strong>问题：以下代码的局部变量表中会占用几个槽？</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test4</span><span class="params">(<span class="type">int</span> k,<span class="type">int</span> m)</span>&#123;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">long</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为了节省空间，局部变量表中的槽是可以复用的，一旦某个局部变量不再生效，当前槽就可以再次被使用。</p>
<p>1、方法执行时，实例对象<code>this</code>、<code>k</code>、<code>m</code> 会被放入局部变量表中，占用3个槽，下标分别是0、1、2。</p>
<p>2、将1的值放入局部变量表下标为3的位置上，相当于给a进行赋值。</p>
<p>3、将2放入局部变量表下标为4的位置，给b赋值为2。</p>
<p>4、ab已经脱离了生效范围，所以下标为3和4的这两个位置可以复用。此时c的值1就可以放入下标为3的位置。</p>
<p>5、脱离c的生效范围之后，给i赋值就可以复用c的位置。</p>
<p>6、最后放入j，j是一个long类型，占用两个槽。但是可以复用b所在的位置，所以占用4和5这两个位置</p>
<p>所以，局部变量表数值的长度为6。这一点在编译期间就可以确定了，运行过程中只需要在栈帧中创建长度为6的数组即可。</p>
<h4 id="操作数栈"><a href="#操作数栈" class="headerlink" title="操作数栈"></a>操作数栈</h4><p>操作数栈是栈帧中虚拟机在执行指令过程中用来存放中间数据的一块区域。他是一种栈式的数据结构，如果一条指令将一个值压入操作数栈，则后面的指令可以弹出并使用该值。</p>
<p><strong>在编译期就可以确定操作数栈的最大深度，从而在执行时正确的分配内存大小。</strong></p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/28/xSaop9pP.png"></p>
<p>比如之前的相加案例中，操作数栈最大的深入会出现在这个时刻：<img src="" data-original="https://img2.imgtp.com/2024/04/28/cZ2o3VJi.png"></p>
<p>所以操作数栈的深度会定义为2。</p>
<h4 id="帧数据"><a href="#帧数据" class="headerlink" title="帧数据"></a>帧数据</h4><p>帧数据主要包含动态链接、方法出口、异常表的引用。</p>
<p><strong>动态链接</strong></p>
<p>当前类的字节码指令引用了其他类的属性或者方法时，需要将符号引用（编号）转换成对应的运行时常量池中的内存地址。动态链接就保存了编号到运行时常量池的内存地址的映射关系。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/28/HTTWCiSG.png"></p>
<p><strong>方法出口</strong></p>
<p>方法出口指的是方法在正确或者异常结束时，当前栈帧会被弹出，同时程序计数器应该指向上一个栈帧中的下一条指令的地址。所以在当前栈帧中，需要存储此方法出口的地址。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/28/AGkiZCPd.png"></p>
<p><strong>异常表</strong></p>
<p>异常表存放的是代码中异常的处理信息，包含了异常捕获的生效范围以及异常发生后跳转到的字节码指令位置。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/28/hJ4jSFG1.png"></p>
<p>如下案例：<code>i=1</code>这行源代码编译成字节码指令之后，会包含偏移量2-4这三行指令。其中2-3是对i进行赋值1的操作，4的没有异常就跳转到10方法结束。如果出现异常的情况下，继续执行到7这行指令，7会将异常对象放入操作数栈中，这样在catch代码块中就可以使用异常对象了。接下来执行8-9，对i进行赋值为2的操作。</p>
<p>所以异常表中，异常捕获的起始偏移量就是2，结束偏移量是4，在2-4执行过程中抛出了<code>java.lang.Exception</code>对象或者子类对象，就会将其捕获，然后跳转到偏移量为7的指令。</p>
<h4 id="栈内存溢出"><a href="#栈内存溢出" class="headerlink" title="栈内存溢出"></a>栈内存溢出</h4><p>Java虚拟机栈帧过多，占用内存超过栈内存可以分配的最大大小就会出现内存溢出。Java虚拟机栈内存溢出时会出现<code>StackOverflowError</code>的错误。</p>
<p>如果我们不指定栈的大小，JVM 将创建一个具有默认大小的栈。大小取决于<strong>操作系统和计算机的体系结构</strong>。几乎都在1MB左右。</p>
<p><strong>我们可以通过添加虚拟机参数来修改虚拟机栈大小</strong></p>
<ul>
<li>语法：-Xss栈大小</li>
<li>单位：字节（默认，必须是 1024 的倍数）、k或者K(KB)、m或者M(MB)、g或者G(GB)</li>
</ul>
<p><strong>注意事项</strong></p>
<p>1、与-Xss类似，也可以使用 -XX:ThreadStackSize 调整标志来配置堆栈大小。</p>
<p>格式为： <code>-XX:ThreadStackSize=1024</code></p>
<p>2、HotSpot JVM对栈大小的最大值和最小值有要求：</p>
<p>比如测试如下两个参数，会直接报错:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-Xss1k</span><br><span class="line">-Xss1025m</span><br></pre></td></tr></table></figure>

<p>Windows（64位）下的JDK8测试最小值为<code>180k</code>，最大值为<code>1024m</code>。</p>
<p>3、局部变量过多、操作数栈深度过大也会影响栈内存的大小。</p>
<p><strong>一般情况下，工作中即便使用了递归进行操作，栈的深度最多也只能到几百,不会出现栈的溢出。所以此参数可以手动指定为-Xss256k节省内存。</strong></p>
<h3 id="本地方法栈"><a href="#本地方法栈" class="headerlink" title="本地方法栈"></a>本地方法栈</h3><p>Java虚拟机栈存储了Java方法调用时的栈帧，而本地方法栈存储的是native本地方法的栈帧。</p>
<p>在Hotspot虚拟机中，Java虚拟机栈和本地方法栈实现上使用了同一个栈空间。本地方法栈会在栈内存上生成一个栈帧，临时保存方法的参数同时方便出现异常时也把本地方法的栈信息打印出来。</p>
<p>比如测试下这段代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 本地方法栈</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NativeDemo1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 并不存在123.txt文件</span></span><br><span class="line">            <span class="type">FileOutputStream</span> <span class="variable">fileOutputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;E:\\123.txt&quot;</span>);</span><br><span class="line">            fileOutputStream.write(<span class="number">1</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (FileNotFoundException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>执行之后发生异常，会打印出所有栈帧的名字：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/29/l0N51Uz8.png"></p>
<p><code>open0</code>是一个本地方法，所以创建了本地方法的栈帧。本地方法和Java虚拟机方法的栈帧在一个栈上。</p>
<h3 id="堆内存"><a href="#堆内存" class="headerlink" title="堆内存"></a>堆内存</h3><p>一般Java程序中堆内存是空间最大的一块内存区域。创建出来的对象都存在于堆上。栈上的局部变量表中，可以存放堆上对象的引用。静态变量也可以存放堆对象的引用，通过静态变量就可以实现对象在线程之间共享。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;        </span><br><span class="line">        <span class="type">Student</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>();        </span><br><span class="line">        s1.name = <span class="string">&quot;张三&quot;</span>;       </span><br><span class="line">        s1.age = <span class="number">18</span>;       </span><br><span class="line">        s1.id = <span class="number">1</span>;</span><br><span class="line">        s1.printTotalScore();        </span><br><span class="line">        s1.printAverageScore();        </span><br><span class="line">        </span><br><span class="line">        <span class="type">Student</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>();       </span><br><span class="line">        s2.name = <span class="string">&quot;李四&quot;</span>;        </span><br><span class="line">        s2.age = <span class="number">19</span>;        </span><br><span class="line">        s2.id= <span class="number">2</span>;        </span><br><span class="line">        s2.printTotalScore();        </span><br><span class="line">        s2.printAverageScore();    </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这段代码中通过<code>new</code>关键字创建了两个<code>Student</code>类的对象，这两个对象会被存放在堆上。在栈上通过<code>s1</code>和<code>s2</code>两个局部变量保存堆上两个对象的地址，从而实现了引用关系的建立。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/29/t8zUJGTW.png"></p>
<h4 id="堆内存移除"><a href="#堆内存移除" class="headerlink" title="堆内存移除"></a>堆内存移除</h4><p>通过new关键字不停创建对象，放入集合中，模拟堆内存的溢出，观察堆溢出之后的异常信息。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> chapter03.heap;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 堆内存的使用和回收</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException, IOException &#123;</span><br><span class="line"></span><br><span class="line">        ArrayList&lt;Object&gt; objects = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Object&gt;();</span><br><span class="line">        System.in.read();</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>)&#123;</span><br><span class="line">            objects.add(<span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>]);</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>堆内存大小是有上限的，当对象一直向堆中放入对象达到上限之后，就会抛出OutOfMemory错误。在这段代码中，不停创建100M大小的字节数组并放入ArrayList集合中，最终超过了堆内存的上限。抛出如下错误：<code>java.lang.OutOfMemory: Java heap space</code></p>
<h4 id="三个重要的值"><a href="#三个重要的值" class="headerlink" title="三个重要的值"></a>三个重要的值</h4><p>堆空间有三个需要关注的值，used、total、max。</p>
<ul>
<li>used指的是当前已使用的堆内存</li>
<li>total是java虚拟机已经分配的可用堆内存</li>
<li>max是java虚拟机可以分配的最大堆内存</li>
</ul>
<p>当used内存等于total内存的时候， Java虚拟机会向操作系统申请更大的内存。但是这个申请过程不是无限的，total最多只能与max相等。</p>
<p>那么是不是当used &#x3D; max &#x3D; total的时候，堆内存就溢出了呢？</p>
<p>不是，堆内存溢出的判断条件比较复杂，在下一章《垃圾回收器》中会详细介绍。</p>
<p>如果不设置任何的虚拟机参数，max默认是系统内存的1&#x2F;4，total默认是系统内存的1&#x2F;64。在实际应用中一般都需要设置total和max的值。</p>
<h4 id="设置堆内存大小"><a href="#设置堆内存大小" class="headerlink" title="设置堆内存大小"></a>设置堆内存大小</h4><p>要修改堆的大小，可以使用虚拟机参数 –Xmx（max最大值）和-Xms (初始的total)。</p>
<p>语法：<code>-Xmx值 -Xms值</code></p>
<p>单位：字节（默认，必须是 1024 的倍数）、k或者K(KB)、m或者M(MB)、g或者G(GB)</p>
<p>限制：Xmx必须大于 2 MB，Xms必须大于1MB</p>
<p><strong>问题：为什么我设置的值和arthas实际显示heap堆的值不一样呢？</strong></p>
<p>arthas中的heap堆内存使用了JMX技术中内存获取方式，这种方式与垃圾回收器有关，计算的是可以分配对象的内存，而不是整个内存。</p>
<p><strong>建议</strong></p>
<p>Java服务端程序开发时，<strong>建议将-Xmx和-Xms设置为相同的值</strong>，这样在程序启动之后可使用的总内存就是最大内存，而无需向java虚拟机再次申请，减少了申请并分配内存时间上的开销，同时也不会出现内存过剩之后堆收缩的情况。</p>
<p>-Xmx具体设置的值与实际的应用程序运行环境有关，在《实战篇》中会给出设置方案。</p>
<h3 id="方法区"><a href="#方法区" class="headerlink" title="方法区"></a>方法区</h3><p>方法区是存放基础信息的位置，线程共享，主要包含三部分内容：</p>
<ul>
<li>类的元信息，保存了所有类的基本信息</li>
<li>运行时常量池，保存了字节码文件中的常量池内容</li>
<li>字符串常量池，保存了字符串常量</li>
</ul>
<h4 id="类的元信息"><a href="#类的元信息" class="headerlink" title="类的元信息"></a>类的元信息</h4><p>方法区是用来存储每个类的基本信息（元信息），一般称之为InstanceKlass对象。在类的加载阶段完成。其中就包含了类的字段、方法等字节码文件中的内容，同时还保存了运行过程中需要使用的虚方法表（实现多态的基础）等信息。</p>
<h4 id="运行时常量池"><a href="#运行时常量池" class="headerlink" title="运行时常量池"></a>运行时常量池</h4><p>方法区除了存储类的元信息之外，还存放了运行时常量池。常量池中存放的是字节码中的常量池内容。</p>
<p>字节码文件中通过编号查表的方式找到常量，这种常量池称为静态常量池。当常量池加载到内存中之后，可以通过内存地址快速的定位到常量池中的内容，这种常量池称为运行时常量池。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/30/RmfW4beQ.png"></p>
<h4 id="字符串常量池"><a href="#字符串常量池" class="headerlink" title="字符串常量池"></a>字符串常量池</h4><p>方法区中除了类的元信息、运行时常量池之外，还有一块区域叫字符串常量池(StringTable)。</p>
<p>字符串常量池存储在代码中定义的常量字符串内容。比如“123” 这个123就会被放入字符串常量池。</p>
<p>如下代码执行时，代码中包含<code>abc</code>字符串，就会被直接放入字符串常量池。在堆上创建String对象，并通过局部变量s1引用堆上的对象。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/30/w776qEYT.png"></p>
<p>接下来通过s2局部变量引用字符串常量池的<code>abc</code>。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/30/yeY6ZFQg.png"></p>
<p>所以s1和s2指向的不是同一个对象，打印出<code>false</code>。</p>
<p><strong>字符串常量池和运行时常量池有什么关系？</strong></p>
<p>早期设计时，字符串常量池是属于运行时常量池的一部分，他们存储的位置也是一致的。后续做出了调整，将字符串常量池和运行时常量池做了拆分。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/04/30/vQLKx9n7.png"></p>
<p><strong>StringTable的练习题1：</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 字符串常量池案例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> <span class="string">&quot;1&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">b</span> <span class="operator">=</span> <span class="string">&quot;2&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&quot;12&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">d</span> <span class="operator">=</span> a + b;</span><br><span class="line">        System.out.println(c == d);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>1、首先将<code>1</code>放入字符串常量池，通过局部变量a引用字符串常量池中的<code>1</code>字符串。</p>
<p>2、同理处理b和c。</p>
<p>3、将a和b指向的字符串进行连接，本质上就是使用StringBuilder进行连接，最后创建了一个新的字符串放入堆中。然后将局部变量d指向堆上的对象。</p>
<p>4、所以c和d指向的不是同一个对象，打印出的结果就是false。</p>
<p><strong>StringTable的练习题2：</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> chapter03.stringtable;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 字符串常量池案例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo3</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> <span class="string">&quot;1&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">b</span> <span class="operator">=</span> <span class="string">&quot;2&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&quot;12&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">d</span> <span class="operator">=</span> <span class="string">&quot;1&quot;</span> + <span class="string">&quot;2&quot;</span>;</span><br><span class="line">        System.out.println(c == d);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在编译阶段，已经将1和2进行连接，最终生成12的字符串常量池中的结果。所以返回结果就是true，c和d都指向字符串常量池中的对象。</p>
<p><strong>神奇的intern</strong></p>
<p>String.intern()方法是可以手动将字符串放入字符串常量池中，分别在JDK6 JDK8下执行代码，JDK6 中结果是false false ，JDK8中是true false</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> chapter03.stringtable;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * intern案例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo4</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>().append(<span class="string">&quot;think&quot;</span>).append(<span class="string">&quot;123&quot;</span>).toString();</span><br><span class="line"></span><br><span class="line">        System.out.println(s1.intern() == s1);</span><br><span class="line"><span class="comment">//        System.out.println(s1.intern() == s1.intern());</span></span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>().append(<span class="string">&quot;ja&quot;</span>).append(<span class="string">&quot;va&quot;</span>).toString();</span><br><span class="line"></span><br><span class="line">        System.out.println(s2.intern() == s2);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>先来分析JDK6中，代码执行步骤如下：</p>
<p>1、使用StringBuilder的将<code>think</code>和<code>123</code>拼接成<code>think123</code>，转换成字符串，在堆上创建一个字符串对象。局部变量<code>s1</code>指向堆上的对象。</p>
<p>2、调用s1.intern方法，会在字符串常量池中创建think123的对象，最后将对象引用返回。所以s1.intern和s1指向的不是同一个对象。s1指向堆中的对象，s1.intern指向字符串常量对象，打印出false。</p>
<p>3、同理，通过StringBuilder在堆上创建java字符串对象。这里注意字符串常量池中本来就有一个java字符串对象，这是java虚拟机自身使用的所以启动时就会创建出来。</p>
<p>4、调用s2.intern发现字符串常量池中已经有java字符串对象了，就将引用返回。所以s2.intern指向的是字符串常量池中的对象，而s2指向的是堆中的对象。打印结果为false。</p>
<p>接下来分析JDK7中，JDK7及之后版本中由于字符串常量池在堆上，所以intern () 方法会把第一次遇到的<strong>字符串的引用</strong>放入字符串常量池。</p>
<p>1、执行第二句代码时，由于字符串常量池中没有think123的字符串，所以直接创建一个引用，指向堆中的think123对象。所以s1.intern和s1指向的都是堆上的对象，打印结果为true。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/02/WiewGXr4.png"></p>
<p>2、s2.intern方法调用时，字符串常量池中已经有java字符串了，所以将引用返回。这样打印出来的结果就是false。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/02/y8cLFSXD.png"></p>
<blockquote>
<p>后续JDK版本中，如果Java虚拟机不需要使用java字符串，那么字符串常量池中就不会存放<code>java</code>。打印结果有可能会出现两个true。</p>
</blockquote>
<h4 id="面试题：静态变量存储在哪里呢？"><a href="#面试题：静态变量存储在哪里呢？" class="headerlink" title="面试题：静态变量存储在哪里呢？"></a>面试题：静态变量存储在哪里呢？</h4><ul>
<li>JDK6及之前的版本中，静态变量是存放在方法区中的，也就是永久代。</li>
<li>JDK7及之后的版本中，静态变量是存放在堆中的Class对象中，脱离了永久代。具体源码可参考虚拟机源码：BytecodeInterpreter针对putstatic指令的处理。</li>
</ul>
<h4 id="方法区的实现"><a href="#方法区的实现" class="headerlink" title="方法区的实现"></a>方法区的实现</h4><p>JDK7及之前的版本将方法区存放在堆区域中的<strong>永久代空间</strong>，堆的大小由虚拟机参数来控制。</p>
<p>JDK8及之后的版本将方法区存放在<strong>元空间</strong>中，元空间位于操作系统维护的直接内存中，默认情况下只要不超过操作系统承受的上限，可以一直分配。</p>
<h3 id="直接内存"><a href="#直接内存" class="headerlink" title="直接内存"></a>直接内存</h3><p>直接内存（Direct Memory）并不在《Java虚拟机规范》中存在，所以并不属于Java运行时的内存区域。</p>
<p>在 JDK 1.4 中引入了 NIO 机制，使用了直接内存，主要为了解决以下两个问题:</p>
<p>1、Java堆中的对象如果不再使用要回收，回收时会影响对象的创建和使用。</p>
<p>2、IO操作比如读文件，需要先把文件读入直接内存（缓冲区）再把数据复制到Java堆中。</p>
<p>现在直接放入直接内存即可，同时Java堆上维护直接内存的引用，减少了数据复制的开销。写文件也是类似的思路。</p>
<p>使用堆创建对象的过程：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/02/Nmzq7tUi.png"></p>
<p>使用直接内存创建对象的过程，不需要进行复制对象，数据直接存放在直接内存中：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/02/XXEA1EQz.png"></p>
<h4 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h4><p>要创建直接内存上的数据，可以使用<code>ByteBuffer</code>。</p>
<p>语法： <code>ByteBuffer directBuffer = ByteBuffer.allocateDirect(size);</code></p>
<p>注意事项： arthas的memory命令可以查看直接内存大小，属性名direct。</p>
<p>代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> chapter03.direct;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 直接内存的使用和回收</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> <span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>; <span class="comment">//100mb</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> List&lt;ByteBuffer&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;ByteBuffer&gt;();</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, InterruptedException &#123;</span><br><span class="line">        System.in.read();</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="comment">//1.创建DirectByteBuffer对象并返回</span></span><br><span class="line">            <span class="comment">//2.在DirectByteBuffer构造方法中，向操作系统申请直接内存空间</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">directBuffer</span> <span class="operator">=</span> ByteBuffer.allocateDirect(size);</span><br><span class="line">            <span class="comment">//directBuffer = null;</span></span><br><span class="line"></span><br><span class="line">            list.add(directBuffer);</span><br><span class="line">            System.out.println(++count);</span><br><span class="line">            Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行之后，用arthas监控发现，每隔5秒直接内存会增长100M</p>
<p>如果将<code>Thread.</code><em><code>sleep</code></em><code>(5000);</code>注释掉，让直接内存快速大量分配。操作系统内存不足时就会报错：<code>java.lang.OutOfMemoryError：Direct buffer Memory</code></p>
<p>是工作中服务器上有可能部署了其他应用，为了避免将内存耗尽，需要设置直接内存的最大值。如果需要手动调整直接内存的大小，可以使用<code>XX:MaxDirectMemorySize=大小</code>，默认不设置该参数情况下，JVM 自动选择 最大分配的大小。</p>
<p>以下示例以不同的单位说明如何将 直接内存大小设置为 1024 KB：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-XX:MaxDirectMemorySize=1m</span><br><span class="line">-XX:MaxDirectMemorySize=1024k</span><br><span class="line">-XX:MaxDirectMemorySize=<span class="number">1048576</span></span><br></pre></td></tr></table></figure>

<h2 id="垃圾回收"><a href="#垃圾回收" class="headerlink" title="垃圾回收"></a>垃圾回收</h2><h3 id="介绍-1"><a href="#介绍-1" class="headerlink" title="介绍"></a>介绍</h3><p>在C&#x2F;C++这类没有自动垃圾回收机制的语言中，一个对象如果不再使用，需要手动释放，否则就会出现内存泄漏。</p>
<p><strong>内存泄漏</strong>指的是不再使用的对象在系统中未被回收，内存泄漏的积累可能会导致内存溢出。   </p>
<p>在这段代码中，通过死循环不停创建Test类的对象，每一轮循环结束之后，这次创建的对象就不再使用了。但是没有手动调用删除对象的方法，此时对象就会出现内存泄漏。     </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(<span class="literal">true</span>)&#123;</span><br><span class="line">        Test *t = <span class="keyword">new</span> <span class="title class_">Test</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    retrun <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这段代码中，手动调用<code>delete</code>删除对象，就不会出现内存泄漏。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(<span class="literal">true</span>)&#123;</span><br><span class="line">        Test *t = <span class="keyword">new</span> <span class="built_in">Test</span>();</span><br><span class="line">        <span class="keyword">delete</span> t;</span><br><span class="line">    &#125;</span><br><span class="line">    retrun <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们称这种释放对象的过程为垃圾回收，而需要程序员编写代码进行回收的方式为手动回收。手动回收的方式相对来说回收比较及时，删除代码执行之后对象就被回收了，可以快速释放内存。缺点是对程序员要求比较高，很容易出现创建完对象之后，程序员忘记释放对象。</p>
<p>Java中为了简化对象的释放，引入了<strong>自动的垃圾回收（Garbage Collection简称GC）</strong>机制。通过垃圾回收器来对不再使用的对象完成自动的回收，垃圾回收器主要负责对堆上的内存进行回收。其他很多现代语言比如C#、Python、Go都拥有自己的垃圾回收器。</p>
<p>垃圾回收器如果发现某个对象不再使用，就可以回收该对象。</p>
<p><strong>自动回收和手动回收的优缺点</strong></p>
<ul>
<li>自动垃圾回收，自动根据对象是否使用由虚拟机来回收对象<ul>
<li>优点：降低程序员实现难度、降低对象回收bug的可能性</li>
<li>缺点：程序员无法控制内存回收的及时性</li>
</ul>
</li>
<li>手动垃圾回收，由程序员编程实现对象的删除<ul>
<li>优点：回收及时性高，由程序员把控回收的时机</li>
<li>缺点：编写不当容易出现悬空指针、重复释放、内存泄漏等问题</li>
</ul>
</li>
</ul>
<p><strong>那么垃圾回收器需要负责对哪些部分的内存进行回收呢？</strong></p>
<p>首先是线程不共享的部分（程序计数器、Java虚拟机、本地方法栈），都是伴随着线程的创建而创建，线程的销毁而销毁。而方法的栈帧在执行完方法之后就会自动弹出栈并释放掉对应的内存。所以这一部分<u>不需要垃圾回收器负责回收</u>。</p>
<h3 id="方法区的回收"><a href="#方法区的回收" class="headerlink" title="方法区的回收"></a>方法区的回收</h3><p>方法区的回收对应的是类的生命周期中的卸载阶段。</p>
<p>方法区中能回收的内容主要就是<strong>不再使用的类</strong>。</p>
<p>判定一个类可以被卸载。需要同时满足下面三个条件：</p>
<p>1、此类所有实例对象都已经被回收，在堆中不存在任何该类的实例对象以及子类对象。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/04/XbC19S11.png"></p>
<p>这段代码中就将局部变量对堆上实例对象的引用去除了，所以对象就可以被回收。</p>
<p>2、加载该类的类加载器已经被回收。</p>
<p>这段代码让局部变量对类加载器的引用去除，类加载器就可以回收。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/04/qveCUTQN.png"></p>
<p>3、该类对应的 java.lang.Class 对象没有在任何地方被引用。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/04/OkmRbaP9.png"></p>
<p>代码:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> chapter04.gc;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.URL;</span><br><span class="line"><span class="keyword">import</span> java.net.URLClassLoader;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 类的卸载</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ClassUnload</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            ArrayList&lt;Class&lt;?&gt;&gt; classes = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">            ArrayList&lt;URLClassLoader&gt; loaders = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">            ArrayList&lt;Object&gt; objs = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line"></span><br><span class="line">                <span class="type">URLClassLoader</span> <span class="variable">loader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">URLClassLoader</span>(</span><br><span class="line">                        <span class="keyword">new</span> <span class="title class_">URL</span>[]&#123;<span class="keyword">new</span> <span class="title class_">URL</span>(<span class="string">&quot;file:D:\\lib\\&quot;</span>)&#125;);</span><br><span class="line">                Class&lt;?&gt; clazz = loader.loadClass(<span class="string">&quot;com.itheima.my.A&quot;</span>);</span><br><span class="line">                <span class="type">Object</span> <span class="variable">o</span> <span class="operator">=</span> clazz.newInstance();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//                objs.add(o);</span></span><br><span class="line"><span class="comment">//                classes.add(clazz);</span></span><br><span class="line"><span class="comment">//                 loaders.add(loader);</span></span><br><span class="line"></span><br><span class="line">                 System.gc();</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line"></span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果注释掉代码中三句add调用，就可以同时满足上述3个条件。但是需要手动调用<code>System.gc()</code>方法，让垃圾回收器进行回收。执行之后，日志中就会打印出类卸载的内容。</p>
<blockquote>
<p>如果需要手动触发垃圾回收，可以调用System.gc()方法。</p>
<p>语法： <code>System.gc()</code></p>
<p>注意事项：调用System.gc()方法并不一定会立即回收垃圾，仅仅是向Java虚拟机发送一个垃圾回收的请求，具体是否需要执行垃圾回收Java虚拟机会自行判断。</p>
</blockquote>
<p><strong>那么类卸载主要用在什么场景下呢？</strong></p>
<p>开发中此类场景一般很少出现，主要在如 OSGi、JSP 的热部署等应用场景中。</p>
<p>每个jsp文件对应一个唯一的类加载器，当一个jsp文件修改了，就直接卸载这个jsp类加载器。重新创建类加载器，重新加载jsp文件。</p>
<h3 id="堆回收"><a href="#堆回收" class="headerlink" title="堆回收"></a>堆回收</h3><h4 id="如何判断对象可以回收"><a href="#如何判断对象可以回收" class="headerlink" title="如何判断对象可以回收"></a>如何判断对象可以回收</h4><p>垃圾回收器要回收对象的第一步就是判断哪些对象可以回收。Java中的对象是否能被回收，是根据对象是否被引用来决定的。如果对象被引用了，说明该对象还在使用，不允许被回收。</p>
<p>案例一</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Demo</span> <span class="variable">d</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Demo</span>();</span><br><span class="line">d = <span class="literal">null</span>;</span><br></pre></td></tr></table></figure>

<p>第一行代码执行之后，堆上创建了Demo类的实例对象，同时栈上保存局部变量引用堆上的对象。</p>
<p>第二行代码执行之后，局部变量对堆上的对象引用去掉，那么堆上的对象就可以被回收了。</p>
<p>案例二：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">A</span> <span class="variable">a1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">A</span>();</span><br><span class="line"><span class="type">B</span> <span class="variable">b1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">B</span>();</span><br><span class="line">a1.b = b1;</span><br><span class="line">b1.a = a1;</span><br></pre></td></tr></table></figure>

<p>如果要让对象a和b回收，必须将局部变量到堆上的引用去除。</p>
<p>那么问题来了，A和B互相之间的引用需要去除吗？答案是不需要，因为局部变量都没引用这两个对象了，在代码中已经无法访问这两个对象，即便他们之间互相有引用关系，也不影响对象的回收。</p>
<p>判断对象是否可以回收，主要有两种方式：<strong>引用计数法</strong>和<strong>可达性分析法</strong>。</p>
<h4 id="引用计数法"><a href="#引用计数法" class="headerlink" title="引用计数法"></a>引用计数法</h4><p>引用计数法会为每个对象维护一个引用计数器，当对象被引用时加1，取消引用时减1。</p>
<p>比如下图中，对象A的计数器初始为0，局部变量a1对它引用之后，计数器加1就变成了1。在A中保存B的引用，同样A对B产生了引用，B的计数器也是1。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/04/JwxSmL0S.png"></p>
<p>引用计数法的优点是实现简单，C++中的智能指针就采用了引用计数法，但是它也存在缺点，主要有两点：</p>
<p>1.每次引用和取消引用都需要维护计数器，对系统性能会有一定的影响</p>
<p>2.存在循环引用问题，所谓循环引用就是当A引用B，B同时引用A时会出现对象无法回收的问题。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/04/qtPC0dKs.png"></p>
<p>这张图上，由于A和B之间存在互相引用，所以计数器都为1，两个对象都不能被回收。但是由于没有局部变量对这两个代码产生引用，代码中已经无法访问到这两个对象，理应可以被回收。</p>
<h4 id="可达性分析法"><a href="#可达性分析法" class="headerlink" title="可达性分析法"></a>可达性分析法</h4><p>Java使用的是可达性分析算法来判断对象是否可以被回收。可达性分析将对象分为两类：<strong>垃圾回收的根对象（GC Root）</strong>和<strong>普通对象</strong>，对象与对象之间存在引用关系。</p>
<p>下图中A到B再到C和D，形成了一个引用链，可达性分析算法指的是如果从某个到GC Root对象是可达的，对象就不可被回收。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/kwCpDNPu.png"></p>
<p>哪些对象被称之为GC Root对象呢？</p>
<ul>
<li>线程Thread对象，引用线程栈帧中的方法参数、局部变量等。</li>
<li>系统类加载器加载的java.lang.Class对象，引用类中的静态变量。</li>
<li>监视器对象，用来保存同步锁synchronized关键字持有的对象。</li>
<li>本地方法调用时使用的全局对象。</li>
</ul>
<h3 id="常见的引用对象"><a href="#常见的引用对象" class="headerlink" title="常见的引用对象"></a>常见的引用对象</h3><p>可达性算法中描述的对象引用，一般指的是强引用，即是GCRoot对象对普通对象有引用关系，只要这层关系存在，普通对象就不会被回收。除了强引用之外，Java中还设计了几种其他引用方式：</p>
<ul>
<li>软引用</li>
<li>弱引用</li>
<li>虚引用</li>
<li>终结器引用</li>
</ul>
<h4 id="软引用"><a href="#软引用" class="headerlink" title="软引用"></a>软引用</h4><p>软引用相对于强引用是一种比较弱的引用关系，如果一个对象只有软引用关联到它，当程序内存不足时，就会将软引用中的数据进行回收。在JDK 1.2版之后提供了SoftReference类来实现软引用，软引用常用于缓存中。</p>
<p>如下图中，对象A被GC Root对象强引用了，同时我们创建了一个软引用SoftReference对象（它本身也是一个对象），软引用对象中引用了对象A。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/uimHYZHh.png"></p>
<p>接下来强引用被去掉之后，对象A暂时还是处于不可回收状态，因为有软引用存在并且内存还够用。</p>
<p>如果内存出现不够用的情况，对象A就处于可回收状态，可以被垃圾回收器回收。</p>
<p><strong>这样做有什么好处？</strong></p>
<p>如果对象A是一个缓存，平时会保存在内存中，如果想访问数据可以快速访问。但是如果内存不够用了，我们就可以将这部分缓存清理掉释放内存。即便缓存没了，也可以从数据库等地方获取数据，不会影响到业务正常运行，这样可以减少内存溢出产生的可能性。</p>
<h5 id="软引用的使用方法"><a href="#软引用的使用方法" class="headerlink" title="软引用的使用方法"></a>软引用的使用方法</h5><p>软引用的执行过程如下：</p>
<p>1.将对象使用软引用包装起来，new SoftReference&lt;对象类型&gt;(对象)。</p>
<p>2.内存不足时，虚拟机尝试进行垃圾回收。</p>
<p>3.如果垃圾回收仍不能解决内存不足的问题，回收软引用中的对象。</p>
<p>4.如果依然内存不足，抛出OutOfMemory异常。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 软引用案例2 - 基本使用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SoftReferenceDemo2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>];</span><br><span class="line">        SoftReference&lt;<span class="type">byte</span>[]&gt; softReference = <span class="keyword">new</span> <span class="title class_">SoftReference</span>&lt;<span class="type">byte</span>[]&gt;(bytes);</span><br><span class="line">        bytes = <span class="literal">null</span>;</span><br><span class="line">        System.out.println(softReference.get());</span><br><span class="line"></span><br><span class="line">        <span class="type">byte</span>[] bytes2 = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>];</span><br><span class="line">        System.out.println(softReference.get());</span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//        byte[] bytes3 = new byte[1024 * 1024 * 100];</span></span><br><span class="line"><span class="comment">//        softReference = null;</span></span><br><span class="line"><span class="comment">//        System.gc();</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//        System.in.read();</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>添加虚拟机参数，限制最大堆内存大小为200m：<code>-Xmx200m</code></p>
<p>执行后发现，第二个100m对象创建之后输出<code>null</code>，软引用中包含的对象已经被回收了。</p>
<h5 id="软引用对象本身怎么回收呢？"><a href="#软引用对象本身怎么回收呢？" class="headerlink" title="软引用对象本身怎么回收呢？"></a>软引用对象本身怎么回收呢？</h5><p>如果软引用对象里边包含的数据已经被回收了，那么软引用对象本身其实也可以被回收了。</p>
<p>SoftReference提供了一套队列机制：</p>
<p>1、软引用创建时，通过构造器传入引用队列</p>
<p>2、在软引用中包含的对象被回收时，该软引用对象会被放入引用队列</p>
<p>3、通过代码遍历引用队列，将SoftReference的强引用删除</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 软引用案例3 - 引用队列使用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SoftReferenceDemo3</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">        ArrayList&lt;SoftReference&gt; softReferences = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        ReferenceQueue&lt;<span class="type">byte</span>[]&gt; queues = <span class="keyword">new</span> <span class="title class_">ReferenceQueue</span>&lt;<span class="type">byte</span>[]&gt;();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>];</span><br><span class="line">            <span class="type">SoftReference</span> <span class="variable">studentRef</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SoftReference</span>&lt;<span class="type">byte</span>[]&gt;(bytes,queues);</span><br><span class="line">            softReferences.add(studentRef);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        SoftReference&lt;<span class="type">byte</span>[]&gt; ref = <span class="literal">null</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> ((ref = (SoftReference&lt;<span class="type">byte</span>[]&gt;) queues.poll()) != <span class="literal">null</span>) &#123;</span><br><span class="line">            count++;</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(count);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最终展示的结果是：<code>9</code></p>
<p>这9个软引用对象中包含的数据已经被回收掉，所以可以手动从ArrayList中去掉，这样就可以释放这9个对象。</p>
<h5 id="软引用的缓存案例"><a href="#软引用的缓存案例" class="headerlink" title="软引用的缓存案例"></a>软引用的缓存案例</h5><p>使用软引用实现学生信息的缓存，能支持内存不足时清理缓存。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> chapter04.soft;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.ref.ReferenceQueue;</span><br><span class="line"><span class="keyword">import</span> java.lang.ref.SoftReference;</span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 软引用案例4 - 学生信息的缓存</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StudentCache</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">StudentCache</span> <span class="variable">cache</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StudentCache</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; ; i++) &#123;</span><br><span class="line">            StudentCache.getInstance().cacheStudent(<span class="keyword">new</span> <span class="title class_">Student</span>(i, String.valueOf(i)));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Map&lt;Integer, StudentRef&gt; StudentRefs;<span class="comment">// 用于Cache内容的存储</span></span><br><span class="line">    <span class="keyword">private</span> ReferenceQueue&lt;Student&gt; q;<span class="comment">// 垃圾Reference的队列</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 继承SoftReference，使得每一个实例都具有可识别的标识。</span></span><br><span class="line">    <span class="comment">// 并且该标识与其在HashMap内的key相同。</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">StudentRef</span> <span class="keyword">extends</span> <span class="title class_">SoftReference</span>&lt;Student&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">Integer</span> <span class="variable">_key</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">StudentRef</span><span class="params">(Student em, ReferenceQueue&lt;Student&gt; q)</span> &#123;</span><br><span class="line">            <span class="built_in">super</span>(em, q);</span><br><span class="line">            _key = em.getId();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构建一个缓存器实例</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">StudentCache</span><span class="params">()</span> &#123;</span><br><span class="line">        StudentRefs = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;Integer, StudentRef&gt;();</span><br><span class="line">        q = <span class="keyword">new</span> <span class="title class_">ReferenceQueue</span>&lt;Student&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 取得缓存器实例</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> StudentCache <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> cache;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 以软引用的方式对一个Student对象的实例进行引用并保存该引用</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">cacheStudent</span><span class="params">(Student em)</span> &#123;</span><br><span class="line">        cleanCache();<span class="comment">// 清除垃圾引用</span></span><br><span class="line">        <span class="type">StudentRef</span> <span class="variable">ref</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StudentRef</span>(em, q);</span><br><span class="line">        StudentRefs.put(em.getId(), ref);</span><br><span class="line">        System.out.println(StudentRefs.size());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 依据所指定的ID号，重新获取相应Student对象的实例</span></span><br><span class="line">    <span class="keyword">public</span> Student <span class="title function_">getStudent</span><span class="params">(Integer id)</span> &#123;</span><br><span class="line">        <span class="type">Student</span> <span class="variable">em</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="comment">// 缓存中是否有该Student实例的软引用，如果有，从软引用中取得。</span></span><br><span class="line">        <span class="keyword">if</span> (StudentRefs.containsKey(id)) &#123;</span><br><span class="line">            <span class="type">StudentRef</span> <span class="variable">ref</span> <span class="operator">=</span> StudentRefs.get(id);</span><br><span class="line">            em = ref.get();</span><br><span class="line">        &#125;</span><br><span class="line"><span class="comment">// 如果没有软引用，或者从软引用中得到的实例是null，重新构建一个实例，</span></span><br><span class="line"><span class="comment">// 并保存对这个新建实例的软引用</span></span><br><span class="line">        <span class="keyword">if</span> (em == <span class="literal">null</span>) &#123;</span><br><span class="line">            em = <span class="keyword">new</span> <span class="title class_">Student</span>(id, String.valueOf(id));</span><br><span class="line">            System.out.println(<span class="string">&quot;Retrieve From StudentInfoCenter. ID=&quot;</span> + id);</span><br><span class="line">            <span class="built_in">this</span>.cacheStudent(em);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> em;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 清除那些所软引用的Student对象已经被回收的StudentRef对象</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">cleanCache</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">StudentRef</span> <span class="variable">ref</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">while</span> ((ref = (StudentRef) q.poll()) != <span class="literal">null</span>) &#123;</span><br><span class="line">            StudentRefs.remove(ref._key);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    // 清除Cache内的全部内容</span></span><br><span class="line"><span class="comment">//    public void clearCache() &#123;</span></span><br><span class="line"><span class="comment">//        cleanCache();</span></span><br><span class="line"><span class="comment">//        StudentRefs.clear();</span></span><br><span class="line"><span class="comment">//        //System.gc();</span></span><br><span class="line"><span class="comment">//        //System.runFinalization();</span></span><br><span class="line"><span class="comment">//    &#125;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span> &#123;</span><br><span class="line">    <span class="type">int</span> id;</span><br><span class="line">    String name;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Student</span><span class="params">(<span class="type">int</span> id, String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.id = id;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getId</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> id;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setId</span><span class="params">(<span class="type">int</span> id)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.id = id;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="弱引用"><a href="#弱引用" class="headerlink" title="弱引用"></a>弱引用</h4><p>弱引用的整体机制和软引用基本一致，区别在于弱引用包含的对象在垃圾回收时，不管内存够不够都会直接被回收。在JDK 1.2版之后提供了WeakReference类来实现弱引用，弱引用主要在ThreadLocal中使用。</p>
<p>弱引用对象本身也可以使用引用队列进行回收。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> chapter04.weak;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.lang.ref.WeakReference;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 弱引用案例 - 基本使用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WeakReferenceDemo2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>];</span><br><span class="line">        WeakReference&lt;<span class="type">byte</span>[]&gt; weakReference = <span class="keyword">new</span> <span class="title class_">WeakReference</span>&lt;<span class="type">byte</span>[]&gt;(bytes);</span><br><span class="line">        bytes = <span class="literal">null</span>;</span><br><span class="line">        System.out.println(weakReference.get());</span><br><span class="line"></span><br><span class="line">        System.gc();</span><br><span class="line"></span><br><span class="line">        System.out.println(weakReference.get());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>执行之后发现gc执行之后，对象已经被回收了，输出<code>null</code>。</p>
<h4 id="虚引用和终结器引用"><a href="#虚引用和终结器引用" class="headerlink" title="虚引用和终结器引用"></a>虚引用和终结器引用</h4><p>这两种引用在常规开发中是不会使用的。</p>
<ul>
<li><p>虚引用也叫幽灵引用&#x2F;幻影引用，不能通过虚引用对象获取到包含的对象。虚引用唯一的用途是当对象被垃圾回收器回收时可以接收到对应的通知。Java中使用PhantomReference实现了虚引用，直接内存中为了及时知道直接内存对象不再使用，从而回收内存，使用了虚引用来实现。</p>
</li>
<li><p>终结器引用指的是在对象需要被回收时，终结器引用会关联对象并放置在Finalizer类中的引用队列中，在稍后由一条由FinalizerThread线程从队列中获取对象，然后执行对象的finalize方法，在对象第二次被回收时，该对象才真正的被回收。在这个过程中可以在finalize方法中再将自身对象使用强引用关联上，但是不建议这样做。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> chapter04.finalreference;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 终结器引用案例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FinalizeReferenceDemo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">FinalizeReferenceDemo</span> <span class="variable">reference</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">alive</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;当前对象还存活&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">finalize</span><span class="params">()</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;finalize()执行了...&quot;</span>);</span><br><span class="line">            <span class="comment">//设置强引用自救</span></span><br><span class="line">            reference = <span class="built_in">this</span>;</span><br><span class="line">        &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="built_in">super</span>.finalize();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        reference = <span class="keyword">new</span> <span class="title class_">FinalizeReferenceDemo</span>();</span><br><span class="line">       test();</span><br><span class="line">       test();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        reference = <span class="literal">null</span>;</span><br><span class="line">        <span class="comment">//回收对象</span></span><br><span class="line">        System.gc();</span><br><span class="line">        <span class="comment">//执行finalize方法的优先级比较低，休眠500ms等待一下</span></span><br><span class="line">        Thread.sleep(<span class="number">500</span>);</span><br><span class="line">        <span class="keyword">if</span> (reference != <span class="literal">null</span>) &#123;</span><br><span class="line">            reference.alive();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;对象已被回收&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="垃圾回收算法"><a href="#垃圾回收算法" class="headerlink" title="垃圾回收算法"></a>垃圾回收算法</h3><p>Java是如何实现垃圾回收的呢？简单来说，垃圾回收要做的有两件事：</p>
<p>1、找到内存中存活的对象</p>
<p>2、释放不再存活对象的内存，使得程序能再次利用这部分空间 </p>
<h4 id="垃圾回收算法的历史和分类"><a href="#垃圾回收算法的历史和分类" class="headerlink" title="垃圾回收算法的历史和分类"></a>垃圾回收算法的历史和分类</h4><p>1960年John McCarthy发布了第一个GC算法：标记-清除算法。</p>
<p>1963年Marvin L. Minsky 发布了复制算法。</p>
<p>本质上后续所有的垃圾回收算法，都是在上述两种算法的基础上优化而来。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/uDZk52k2.png"></p>
<h4 id="垃圾回收算法的评价标准"><a href="#垃圾回收算法的评价标准" class="headerlink" title="垃圾回收算法的评价标准"></a>垃圾回收算法的评价标准</h4><p>Java垃圾回收过程会通过单独的GC线程来完成，但是不管使用哪一种GC算法，都会有部分阶段需要停止所有的用户线程。这个过程被称之为Stop The World简称STW，如果STW时间过长则会影响用户的使用。</p>
<p>如下图，用户代码执行和垃圾回收执行让用户线程停止执行（STW）是交替执行的。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/koFP5rWJ.png"></p>
<p>所以判断GC算法是否优秀，可以从三个方面来考虑：</p>
<p><strong>1.吞吐量</strong></p>
<p>吞吐量指的是 CPU 用于执行用户代码的时间与 CPU 总执行时间的比值，即吞吐量 &#x3D; 执行用户代码时间 &#x2F;（执行用户代码时间 + GC时间）。吞吐量数值越高，垃圾回收的效率就越高。</p>
<p>比如虚拟机总共运行了 100分钟，其中GC花费了1分钟，那么吞吐量就是99%</p>
<p><strong>2.最大暂停时间</strong></p>
<p>最大暂停时间指的是所有在垃圾回收过程中的STW时间最大值。比如如下的图中，黄色部分的STW就是最大暂停时间，显而易见上面的图比下面的图拥有更少的最大暂停时间。最大暂停时间越短，用户使用系统时受到的影响就越短。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/nV9WUgta.png"></p>
<p><strong>3.堆使用效率</strong></p>
<p>不同垃圾回收算法，对堆内存的使用方式是不同的。比如标记清除算法，可以使用完整的堆内存。而复制算法会将堆内存一分为二，每次只能使用一半内存。从堆使用效率上来说，标记清除算法要优于复制算法。</p>
<p>上述三种评价标准：堆使用效率、吞吐量，以及最大暂停时间不可兼得。</p>
<p>一般来说，堆内存越大，最大暂停时间就越长。想要减少最大暂停时间，就会降低吞吐量。</p>
<p>没有一个垃圾回收算法能兼顾上述三点评价标准，所以不同的垃圾回收算法它的侧重点是不同的，适用于不同的应用场景。</p>
<h4 id="标记清除算法"><a href="#标记清除算法" class="headerlink" title="标记清除算法"></a>标记清除算法</h4><p>标记清除算法的核心思想分为两个阶段：</p>
<p>1.标记阶段，将所有存活的对象进行标记。Java中使用可达性分析算法，从GC Root开始通过引用链遍历出所有存活对象。</p>
<p>2.清除阶段，从内存中删除没有被标记也就是非存活对象。</p>
<p><strong>优点</strong>：</p>
<p>实现简单，只需要在第一阶段给每个对象维护标志位，第二阶段删除对象即可。</p>
<p><strong>缺点</strong>：</p>
<p>1.碎片化问题</p>
<p>由于内存是连续的，所以在对象被删除之后，内存中会出现很多细小的可用内存单元。如果我们需要的是一个比较大的空间，很有可能这些内存单元的大小过小无法进行分配。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/DbcNaw9J.png"></p>
<p>2.分配速度慢。由于内存碎片的存在，需要维护一个空闲链表，极有可能发生每次需要遍历到链表的最后才能获得合适的内存空间。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/TOXKnpyO.png"></p>
<h4 id="复制算法"><a href="#复制算法" class="headerlink" title="复制算法"></a>复制算法</h4><p>复制算法的核心思想是：</p>
<p>1.准备两块空间From空间和To空间，每次在对象分配阶段，只能使用其中一块空间（From空间）。</p>
<p>2.在垃圾回收GC阶段，将From中存活对象复制到To空间，然后就清空From空间，此时没有存活的对象就被清除了。</p>
<p>3.将两块空间的From和To名字互换。</p>
<p><strong>优点：</strong></p>
<ul>
<li>吞吐量高，复制算法只需要遍历一次存活对象复制到To空间即可，比标记-整理算法少了一次遍历的过程，因而性能较好，但是不如标记-清除算法，因为标记清除算法不需要进行对象的移动</li>
<li>不会发生碎片化，复制算法在复制之后就会将对象按顺序放入To空间中，所以对象以外的区域都是可用空间，不存在碎片化内存空间。</li>
</ul>
<p><strong>缺点：</strong></p>
<p>内存使用效率低，每次只能让一半的内存空间来为创建对象使用。</p>
<h4 id="标记整理算法"><a href="#标记整理算法" class="headerlink" title="标记整理算法"></a>标记整理算法</h4><p>标记整理算法也叫标记压缩算法，是对标记清理算法中容易产生内存碎片问题的一种解决方案。</p>
<p>核心思想分为两个阶段：</p>
<p>1.标记阶段，将所有存活的对象进行标记。Java中使用可达性分析算法，从GC Root开始通过引用链遍历出所有存活对象。</p>
<p>2.整理阶段，将存活对象移动到堆的一端。清理掉存活对象的内存空间。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/pRHFdv49.png"></p>
<p><strong>优点：</strong></p>
<ul>
<li>内存使用效率高，整个堆内存都可以使用，不会像复制算法只能使用半个堆内存</li>
<li>不会发生碎片化，在整理阶段可以将对象往内存的一侧进行移动，剩下的空间都是可以分配对象的有效空间</li>
</ul>
<p><strong>缺点：</strong></p>
<p>整理阶段的效率不高，整理算法有很多种，比如Lisp2整理算法需要对整个堆中的对象搜索3次，整体性能不佳。可以通过Two-Finger、表格算法、ImmixGC等高效的整理算法优化此阶段的性能。</p>
<h4 id="分代垃圾回收算法"><a href="#分代垃圾回收算法" class="headerlink" title="分代垃圾回收算法"></a>分代垃圾回收算法</h4><p>现代优秀的垃圾回收算法，会将上述描述的垃圾回收算法组合进行使用，其中应用最广的就是**分代垃圾回收算法(**Generational GC)。</p>
<p>分代垃圾回收将整个内存区域划分为年轻代和老年代：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/ilweNwDF.png"></p>
<p>1、分代回收时，创建出来的对象，首先会被放入Eden伊甸园区</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/OzBC3AFX.png"></p>
<p>2、随着对象在Eden区越来越多，如果Eden区满，新创建的对象已经无法放入，就会触发年轻代的GC，称为Minor GC或者Young GC。</p>
<p>Minor GC会把需要eden中和From需要回收的对象回收，把没有回收的对象放入To区。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/RC2GJlMX.png"></p>
<p>3、接下来，S0会变成To区，S1变成From区。当eden区满时再往里放入对象，依然会发生Minor GC。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/IaQhQFtF.png"></p>
<p>此时会回收eden区和S1(from)中的对象，并把eden和from区中剩余的对象放入S0。</p>
<p>注意：每次Minor GC中都会为对象记录他的年龄，初始值为0，每次GC完加1。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/IA49C8j4.png"></p>
<p>4、如果Minor GC后对象的年龄达到阈值（最大15，默认值和垃圾回收器有关），对象就会被晋升至老年代。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/sxRA95gL.png"></p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/Q2JkGc17.png"></p>
<p>当老年代中空间不足，无法放入新的对象时，先尝试minor gc如果还是不足，就会触发Full GC，Full GC会对整个堆进行垃圾回收。</p>
<p>如果Full GC依然无法回收掉老年代的对象，那么当对象继续放入老年代时，就会抛出Out Of Memory异常。</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/33U8SIuq.png"></p>
<p>测试代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//-XX:+UseSerialGC  -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3  -XX:+PrintGCDetails</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GcDemo0</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        List&lt;Object&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>)&#123;</span><br><span class="line">            System.in.read();</span><br><span class="line">            System.out.println(++count);</span><br><span class="line">            <span class="comment">//每次添加1m的数据</span></span><br><span class="line">            list.add(<span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">1</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>结果如下：</p>
<p><img src="" data-original="https://img2.imgtp.com/2024/05/06/H71mhlBR.png"></p>
<p>老年代已经满了，而且垃圾回收无法回收掉对象，如果还想往里面放就发生了<code>OutOfMemoryError</code>。</p>
<h3 id="垃圾回收器"><a href="#垃圾回收器" class="headerlink" title="垃圾回收器"></a>垃圾回收器</h3><p>为什么分代GC算法要把堆分成年轻代和老年代？首先我们要知道堆内存中对象的特性：</p>
<ul>
<li>系统中的大部分对象，都是创建出来之后很快就不再使用可以被回收，比如用户获取订单数据，订单数据返回给用户之后就可以释放了。</li>
<li>老年代中会存放长期存活的对象，比如Spring的大部分bean对象，在程序启动之后就不会被回收了。</li>
<li>在虚拟机的默认设置中，新生代大小要远小于老年代的大小。</li>
</ul>
<p>分代GC算法将堆分成年轻代和老年代主要原因有：</p>
<p>1、可以通过调整年轻代和老年代的比例来适应不同类型的应用程序，提高内存的利用率和性能。</p>
<p>2、新生代和老年代使用不同的垃圾回收算法，新生代一般选择复制算法，老年代可以选择标记-清除和标记-整理算法，由程序员来选择灵活度较高。</p>
<p>3、分代的设计中允许只回收新生代（minor gc），如果能满足对象分配的要求就不需要对整个堆进行回收(full gc),STW时间就会减少。</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta"><i class="fas fa-circle-user fa-fw"></i>文章作者: </span><span class="post-copyright-info"><a href="http://www.lijunxi.site">Jixer</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta"><i class="fas fa-square-arrow-up-right fa-fw"></i>文章链接: </span><span class="post-copyright-info"><a href="http://www.lijunxi.site/posts/3771679189/">http://www.lijunxi.site/posts/3771679189/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta"><i class="fas fa-circle-exclamation fa-fw"></i>版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="http://www.lijunxi.site" target="_blank">Jixer的小屋</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Java/">Java</a></div><div class="post_share"><div class="social-share" data-image="https://q1.qlogo.cn/g?b=qq&amp;nk=2770063826&amp;s=640" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="/pluginsSrc/butterfly-extsrc/sharejs/dist/css/share.min.css?v=1.1.3" media="print" onload="this.media='all'"><script src="/pluginsSrc/butterfly-extsrc/sharejs/dist/js/social-share.min.js?v=1.1.3" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/posts/604484610/" title="第十三届蓝桥杯B组省赛"><div class="cover" style="background: var(--default-bg-color)"></div><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">第十三届蓝桥杯B组省赛</div></div></a></div><div class="next-post pull-right"><a href="/posts/3364565471/" title="2023年团体程序设计天梯赛"><div class="cover" style="background: var(--default-bg-color)"></div><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">2023年团体程序设计天梯赛</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/posts/2996697887/" title="JavaWeb学习笔记"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2024-01-17</div><div class="title">JavaWeb学习笔记</div></div></a></div><div><a href="/posts/669583838/" title="Java错题集"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2024-01-17</div><div class="title">Java错题集</div></div></a></div><div><a href="/posts/3692994522/" title="Java面试题基础(下)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2024-02-26</div><div class="title">Java面试题基础(下)</div></div></a></div><div><a href="/posts/3305483931/" title="Java面试题基础(上)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2024-02-24</div><div class="title">Java面试题基础(上)</div></div></a></div><div><a href="/posts/532684030/" title="Java面试题基础(中)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2024-02-25</div><div class="title">Java面试题基础(中)</div></div></a></div><div><a href="/posts/1952430746/" title="Java面试题并发(上)"><div class="cover" style="background: var(--default-bg-color)"></div><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2024-03-13</div><div class="title">Java面试题并发(上)</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="" data-original="https://q1.qlogo.cn/g?b=qq&amp;nk=2770063826&amp;s=640" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">Jixer</div><div class="author-info__description"></div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">52</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">19</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/2770063826"><i class="fab fa-github"></i><span>Follow Me</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content"></div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%88%9D%E5%A7%8BJVM"><span class="toc-text">初始JVM</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AFJVM"><span class="toc-text">什么是JVM</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#JVM%E5%8A%9F%E8%83%BD"><span class="toc-text">JVM功能</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%A7%A3%E9%87%8A%E8%BF%90%E8%A1%8C"><span class="toc-text">解释运行</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86"><span class="toc-text">内存管理</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91"><span class="toc-text">即时编译</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Java%E6%80%A7%E8%83%BD%E5%92%8C%E8%B7%A8%E5%B9%B3%E5%8F%B0%E6%80%A7"><span class="toc-text">Java性能和跨平台性</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B8%B8%E8%A7%81%E7%9A%84JVM"><span class="toc-text">常见的JVM</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E7%9A%84%E7%BB%84%E6%88%90"><span class="toc-text">Java虚拟机的组成</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AD%97%E8%8A%82%E7%A0%81%E6%96%87%E4%BB%B6%E7%9A%84%E7%BB%84%E6%88%90"><span class="toc-text">字节码文件的组成</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%B8%B8%E9%87%8F%E6%B1%A0"><span class="toc-text">常量池</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%AD%97%E6%AE%B5"><span class="toc-text">字段</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%95"><span class="toc-text">方法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%B1%9E%E6%80%A7"><span class="toc-text">属性</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AD%97%E8%8A%82%E7%A0%81%E6%96%87%E4%BB%B6%E5%B7%A5%E5%85%B7"><span class="toc-text">字节码文件工具</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%B1%BB%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><span class="toc-text">类的生命周期</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8A%A0%E8%BD%BD%E9%98%B6%E6%AE%B5"><span class="toc-text">加载阶段</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BF%9E%E6%8E%A5%E9%98%B6%E6%AE%B5"><span class="toc-text">连接阶段</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%A1%E9%AA%8C"><span class="toc-text">校验</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%87%86%E5%A4%87"><span class="toc-text">准备</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%A7%A3%E6%9E%90"><span class="toc-text">解析</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%B6%E6%AE%B5"><span class="toc-text">初始化阶段</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%9D%A2%E8%AF%95%E9%A2%981"><span class="toc-text">面试题1</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#clinit%E4%B8%8D%E4%BC%9A%E6%89%A7%E8%A1%8C%E7%9A%84%E5%87%A0%E7%A7%8D%E6%83%85%E5%86%B5"><span class="toc-text">clinit不会执行的几种情况</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%9D%A2%E8%AF%95%E9%A2%982"><span class="toc-text">面试题2</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%A1%A5%E5%85%85%E7%BB%83%E4%B9%A0%E9%A2%98"><span class="toc-text">补充练习题</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">类加载器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%8B%E7%BB%8D"><span class="toc-text">介绍</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%88%86%E7%B1%BB"><span class="toc-text">分类</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%90%AF%E5%8A%A8%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">启动类加载器</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%94%A8%E6%88%B7%E6%89%A9%E5%B1%95%E5%9F%BA%E7%A1%80jar%E5%8C%85"><span class="toc-text">用户扩展基础jar包</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%89%A9%E5%B1%95%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%92%8C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">扩展类加载器和应用程序类加载器</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%8B%93%E5%B1%95%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">拓展类加载器</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%BA%94%E7%94%A8%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">应用加载器</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#JDK9%E4%B9%8B%E5%90%8E%E7%9A%84%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">JDK9之后的类加载器</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6"><span class="toc-text">双亲委派机制</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A1%88%E4%BE%8B2"><span class="toc-text">案例2</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6%E7%9A%84%E4%BD%9C%E7%94%A8"><span class="toc-text">双亲委派机制的作用</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E6%8C%87%E5%AE%9A%E5%8A%A0%E8%BD%BD%E7%B1%BB%E7%9A%84%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%EF%BC%9F"><span class="toc-text">如何指定加载类的类加载器？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9D%A2%E8%AF%95%E9%A2%98"><span class="toc-text">面试题</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%89%93%E7%A0%B4%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6"><span class="toc-text">打破双亲委派机制</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">自定义类加载器</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6%E6%B5%81%E7%A8%8B"><span class="toc-text">双亲委派机制流程</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BB%A3%E7%A0%81%E6%89%93%E7%A0%B4%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6"><span class="toc-text">自定义代码打破双亲委派机制</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E7%88%B6%E7%B1%BB%E6%80%8E%E4%B9%88%E6%98%AFAppClassLoader%E5%91%A2%EF%BC%9F"><span class="toc-text">自定义类加载器父类怎么是AppClassLoader呢？</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%A4%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%8A%A0%E8%BD%BD%E7%9B%B8%E5%90%8C%E9%99%90%E5%AE%9A%E5%90%8D%E7%9A%84%E7%B1%BB%EF%BC%8C%E4%B8%8D%E4%BC%9A%E5%86%B2%E7%AA%81%E5%90%97%EF%BC%9F"><span class="toc-text">两个自定义类加载器加载相同限定名的类，不会冲突吗？</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BA%BF%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">线程上下文类加载器</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#JDBC%E6%A1%88%E4%BE%8B%E4%B8%AD%E7%9C%9F%E7%9A%84%E6%89%93%E7%A0%B4%E4%BA%86%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6%E5%90%97%EF%BC%9F"><span class="toc-text">JDBC案例中真的打破了双亲委派机制吗？</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Osgi%E6%A1%86%E6%9E%B6%E7%9A%84%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">Osgi框架的类加载器</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA"><span class="toc-text">运行时数据区</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%A8%8B%E5%BA%8F%E8%AE%A1%E6%95%B0%E5%99%A8"><span class="toc-text">程序计数器</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%A8%8B%E5%BA%8F%E8%AE%A1%E6%95%B0%E5%99%A8%E4%BC%9A%E5%87%BA%E7%8E%B0%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA%E5%90%97%EF%BC%9F"><span class="toc-text">程序计数器会出现内存溢出吗？</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88"><span class="toc-text">Java虚拟机栈</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E8%A1%A8"><span class="toc-text">局部变量表</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%93%8D%E4%BD%9C%E6%95%B0%E6%A0%88"><span class="toc-text">操作数栈</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%B8%A7%E6%95%B0%E6%8D%AE"><span class="toc-text">帧数据</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%88%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA"><span class="toc-text">栈内存溢出</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9C%AC%E5%9C%B0%E6%96%B9%E6%B3%95%E6%A0%88"><span class="toc-text">本地方法栈</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A0%86%E5%86%85%E5%AD%98"><span class="toc-text">堆内存</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A0%86%E5%86%85%E5%AD%98%E7%A7%BB%E9%99%A4"><span class="toc-text">堆内存移除</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%89%E4%B8%AA%E9%87%8D%E8%A6%81%E7%9A%84%E5%80%BC"><span class="toc-text">三个重要的值</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%AE%BE%E7%BD%AE%E5%A0%86%E5%86%85%E5%AD%98%E5%A4%A7%E5%B0%8F"><span class="toc-text">设置堆内存大小</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%96%B9%E6%B3%95%E5%8C%BA"><span class="toc-text">方法区</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%B1%BB%E7%9A%84%E5%85%83%E4%BF%A1%E6%81%AF"><span class="toc-text">类的元信息</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%BF%90%E8%A1%8C%E6%97%B6%E5%B8%B8%E9%87%8F%E6%B1%A0"><span class="toc-text">运行时常量池</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%B8%B8%E9%87%8F%E6%B1%A0"><span class="toc-text">字符串常量池</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%9D%A2%E8%AF%95%E9%A2%98%EF%BC%9A%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F%E5%AD%98%E5%82%A8%E5%9C%A8%E5%93%AA%E9%87%8C%E5%91%A2%EF%BC%9F"><span class="toc-text">面试题：静态变量存储在哪里呢？</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%95%E5%8C%BA%E7%9A%84%E5%AE%9E%E7%8E%B0"><span class="toc-text">方法区的实现</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98"><span class="toc-text">直接内存</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95"><span class="toc-text">使用方法</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6"><span class="toc-text">垃圾回收</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%8B%E7%BB%8D-1"><span class="toc-text">介绍</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%96%B9%E6%B3%95%E5%8C%BA%E7%9A%84%E5%9B%9E%E6%94%B6"><span class="toc-text">方法区的回收</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A0%86%E5%9B%9E%E6%94%B6"><span class="toc-text">堆回收</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E5%AF%B9%E8%B1%A1%E5%8F%AF%E4%BB%A5%E5%9B%9E%E6%94%B6"><span class="toc-text">如何判断对象可以回收</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0%E6%B3%95"><span class="toc-text">引用计数法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8F%AF%E8%BE%BE%E6%80%A7%E5%88%86%E6%9E%90%E6%B3%95"><span class="toc-text">可达性分析法</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B8%B8%E8%A7%81%E7%9A%84%E5%BC%95%E7%94%A8%E5%AF%B9%E8%B1%A1"><span class="toc-text">常见的引用对象</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%BD%AF%E5%BC%95%E7%94%A8"><span class="toc-text">软引用</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E8%BD%AF%E5%BC%95%E7%94%A8%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95"><span class="toc-text">软引用的使用方法</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E8%BD%AF%E5%BC%95%E7%94%A8%E5%AF%B9%E8%B1%A1%E6%9C%AC%E8%BA%AB%E6%80%8E%E4%B9%88%E5%9B%9E%E6%94%B6%E5%91%A2%EF%BC%9F"><span class="toc-text">软引用对象本身怎么回收呢？</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E8%BD%AF%E5%BC%95%E7%94%A8%E7%9A%84%E7%BC%93%E5%AD%98%E6%A1%88%E4%BE%8B"><span class="toc-text">软引用的缓存案例</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%BC%B1%E5%BC%95%E7%94%A8"><span class="toc-text">弱引用</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%99%9A%E5%BC%95%E7%94%A8%E5%92%8C%E7%BB%88%E7%BB%93%E5%99%A8%E5%BC%95%E7%94%A8"><span class="toc-text">虚引用和终结器引用</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95"><span class="toc-text">垃圾回收算法</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95%E7%9A%84%E5%8E%86%E5%8F%B2%E5%92%8C%E5%88%86%E7%B1%BB"><span class="toc-text">垃圾回收算法的历史和分类</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95%E7%9A%84%E8%AF%84%E4%BB%B7%E6%A0%87%E5%87%86"><span class="toc-text">垃圾回收算法的评价标准</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95"><span class="toc-text">标记清除算法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A4%8D%E5%88%B6%E7%AE%97%E6%B3%95"><span class="toc-text">复制算法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%95"><span class="toc-text">标记整理算法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%88%86%E4%BB%A3%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95"><span class="toc-text">分代垃圾回收算法</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8"><span class="toc-text">垃圾回收器</span></a></li></ol></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/2029624507/" title="2022年算法队选拔赛">2022年算法队选拔赛</a><time datetime="2024-05-09T15:00:27.000Z" title="发表于 2024-05-09 23:00:27">2024-05-09</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/1978524057/" title="牛客小白月赛84">牛客小白月赛84</a><time datetime="2024-05-08T14:40:35.000Z" title="发表于 2024-05-08 22:40:35">2024-05-08</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/131339317/" title="软件测试资料">软件测试资料</a><time datetime="2024-05-07T03:12:52.000Z" title="发表于 2024-05-07 11:12:52">2024-05-07</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/2394234105/" title="第十四届蓝桥杯B组国赛">第十四届蓝桥杯B组国赛</a><time datetime="2024-05-05T13:40:15.000Z" title="发表于 2024-05-05 21:40:15">2024-05-05</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/1405472621/" title="Leetcode第396场周赛">Leetcode第396场周赛</a><time datetime="2024-05-05T03:58:25.000Z" title="发表于 2024-05-05 11:58:25">2024-05-05</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2024 By Jixer</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div><div class="footer_custom_text"><a href="https://beian.miit.gov.cn/#/Integrated/index" style="color:white" target="_blank">蜀ICP备2022009955号-1</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js?v=4.13.0"></script><script src="/js/main.js?v=4.13.0"></script><script src="/pluginsSrc/@fancyapps/ui/dist/fancybox/fancybox.umd.js?v=5.0.33"></script><div class="js-pjax"></div><script src="/js/custom-fancybox-umd-min.js"></script><script src="/js/custom-busuanzi-pure-mini.js"></script><script src="/js/Valine.min.js"></script><script src="/js/custom-social-share.min.js"></script><script src="/js/custom-typed-umd-min.js"></script><script src="/js/av-min.js"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div><div id="local-search-stats-wrap"></div></div></div><div id="search-mask"></div><script src="/js/search/local-search.js?v=4.13.0"></script></div></div>
        <style>
            [bg-lazy] {
                background-image: none !important;
                background-color: #eee !important;
            }
        </style>
        <script>
            window.imageLazyLoadSetting = {
                isSPA: false,
                preloadRatio: 1,
                processImages: null,
            };
        </script><script>window.addEventListener("load",function(){var t=/\.(gif|jpg|jpeg|tiff|png)$/i,r=/^data:image\/[a-z]+;base64,/;Array.prototype.slice.call(document.querySelectorAll("img[data-original]")).forEach(function(a){var e=a.parentNode;"A"===e.tagName&&(e.href.match(t)||e.href.match(r))&&(e.href=a.dataset.original)})});</script><script>!function(r){r.imageLazyLoadSetting.processImages=t;var e=r.imageLazyLoadSetting.isSPA,n=r.imageLazyLoadSetting.preloadRatio||1,c=a();function a(){var t=Array.prototype.slice.call(document.querySelectorAll("img[data-original]")),e=Array.prototype.slice.call(document.querySelectorAll("[bg-lazy]"));return t.concat(e)}function t(){e&&(c=a());for(var t,o=0;o<c.length;o++)0<=(t=(t=c[o]).getBoundingClientRect()).bottom&&0<=t.left&&t.top<=(r.innerHeight*n||document.documentElement.clientHeight*n)&&function(){var t,e,n,a,i=c[o];e=function(){c=c.filter(function(t){return i!==t}),r.imageLazyLoadSetting.onImageLoaded&&r.imageLazyLoadSetting.onImageLoaded(i)},(t=i).hasAttribute("bg-lazy")?(t.removeAttribute("bg-lazy"),e&&e()):(n=new Image,a=t.getAttribute("data-original"),n.onload=function(){t.src=a,t.removeAttribute("data-original"),e&&e()},t.src!==a&&(n.src=a))}()}function i(){clearTimeout(t.tId),t.tId=setTimeout(t,500)}t(),document.addEventListener("scroll",i),r.addEventListener("resize",i),r.addEventListener("orientationchange",i)}(this);</script></body></html>